SwiftUI list flashes my data then shows an empty list using ObservableObject to get Firestore data

Viewed 742

Here is the scenario: Onappear my view calls the getData method of my ObservableObject Class. During debug I see that it retrieves the data and returns the array as expected. When my view appears on the simulator the data flashes briefly on the list, then the list goes blank. I use this same pattern in several places in my application and in some cases it works (the list appears as it should) and in others it goes blank.

I'm testing on both 13.0 and 13.3 simulators due to various swiftUI bugs that work on one or the other. I thought it may be because of the async call to Firestore, but I've implemented completion handlers. IF I change the ObservedObject to a @State then the list appears correctly, but when new items are added it doesn't refresh.

Here is code for one of the examples that is not working:

My VIEW:

import SwiftUI
import Firebase 

struct ManagerListView: View {
    var circuitId: String
    @State private var isMgr:Bool = true //TODO: reset to false
    @EnvironmentObject var session : SessionStore
    @ObservedObject var managerArray = ManagerList()


    func deleteMgrDb(manager: Manager) {
        let docRef = Firestore.firestore().collection("circuits").document(circuitId).collection("managers").document(manager.id)

        docRef.delete { (error) in
            if error != nil {
                print(error!.localizedDescription)
            }
        }
    }
     var body: some View {
        List {
            ForEach(managerArray.managers) { i in

                Text("\(i.firstName) \(i.lastName)  \(i.phone)").deleteDisabled(!self.isMgr)
            }.onDelete { (indexset) in
                let manager = self.managerArray.managers[indexset.first!]
                print("Here \(self.managerArray.managers[indexset.first!])")
                //TODO:  Check if owner (can't remove owner) or maybe if at least one manager (then delete owner?)
                self.managerArray.managers.remove(atOffsets: indexset)
                self.deleteMgrDb(manager: manager)
            }.disabled(!isMgr)

        }.onAppear(perform: {
            self.managerArray.getData(forcircuitid: self.circuitId) { (error) in
                if error != nil {
                    //TODO: Return data error to user
                    print(error!.localizedDescription)
                }
                //TODO: isMgr
            }
        }).onDisappear(perform: {
             self.managerArray.stopMgrListListener()
        }).navigationBarItems(trailing: isMgr ?
            NavigationLink(destination: CircuitManagerAddView(circuitId: self.circuitId, currentmgrs: managerArray.managers), label: {
                Image(systemName: "plus")
            }): nil) //.disabled(!isMgr))
     }
}

My Observable Class:

class ManagerList: ObservableObject {
    @Published var managers = [Manager]()

    var listener: ListenerRegistration?

    func getData(forcircuitid: String, completion: @escaping (Error?)->Void) {

        let db = Firestore.firestore().collection("circuits").document(forcircuitid).collection("managers")

        self.managers.removeAll()

        listener = db.addSnapshotListener { (snapshot, error) in
            if error != nil {
                print(error!.localizedDescription)
                completion(error)
            }
            else {
                for i in snapshot!.documentChanges {
                    if i.type == .added {
                        let email  = i.document.get("email") as! String
                        let lastName = i.document.get("lastname") as! String
                        let firstName = i.document.get("firstname") as! String
                        let phone = i.document.get("phonenum") as! String

                        let manager = Manager(id: email, lastName: lastName, firstName: firstName, phone: phone)

                        self.managers.append(manager)

                        //TODO:  Changed
                    }
                }
                completion(nil)
            }
        }
    }

For my lists that are working, I am using basically the exact same code except changing the Firestore documents, data elements, variable names, etc.. They even run the same way in the debugger, only difference is some work and display the list and others don't.

1 Answers

The list items are disappearing because ManagerListView is re-initialised every time your ManagerList data changes. When that happens ManagerList is also re-initialised but the code you have executing in onAppear is only called the first time the view appears and not on subsequent data changes.

SwiftUI makes no guarantees about the lifetime of @ObservedObject variables. You have to manage that yourself. In iOS 14 they introduced @StateObject to make this easier. You can read more here.

As a side note, onDisappear is only called when your view is no longer being displayed. It would be better to remove the Firebase listener in ManagerList from within the classes de-initialiser function.

Related