Error attempting to connect to Firestore

I am watching Chris’s video “Cloud Firestore Get Data (and other operations) with SwiftUI” but am running into an error connecting to Firestore.
Like in the video, my code is:

var database = Firestore.firestore()
    
    func getData() {
        
        database.collection("MyList").getDocuments { snapshot, error in
             if error == nil {
                print ("this might work")
                if let snapshot = snapshot {
                    
                    DispatchQueue.main.async{
                        self.people = snapshot.documents.map { d in
                            print("found \(d["firstName"]) id: \(d.documentID)")
                            return Person(
                                         id: d.documentID,
                                         firstName: d["firstName"] as? String ?? "",
                                         lastName: d["lastName"] as? String ?? ""
                            )
                        }
                    }
                }
                print("getData done\(self.people)")
            }
            else {
               print("Something went wrong")
            }
        }
    }

but on that last line I get an error that says “Trailing closure passed to parameter of type ‘FirestoreSource’ that does not accept a closure”. I have tried fooling around with a few syntax changes and searched online but cannot find a solution. I am using Firebase 10.10 and Xcode 14.3.1 so I should have the latest packages. Does anyone know what could be causing this error?

@bRow

Welcome to the community.

Can you paste the entire code you have for that getData() function so that I can see how you have structured it.

I am assuming that you are following the video and using the same code word for word otherwise that might explain why you are getting errors.

Apologies, I have posted my full code for that function. There are print statements for debugging purposes but none of them fire since I cannot compile with the current error. I have been unable to add or get data from the Firestore, but when experimenting with RealTime database I was able to add data so I believe I am connected to Firebase.

@bRow

Interesting. I placed the code in a test Application I have here and the syntax is correct. The only thing I would do differently is to have the reference to the Firestore database placed inside the function like this but operationally it still works the way you have it.

func getData() {
        let db = Firestore.firestore()
        
        db.collection("MyList").getDocuments { snapshot, error in
            if error == nil {
                print ("this might work")
                if let snapshot = snapshot {

                    DispatchQueue.main.async{
                        self.people = snapshot.documents.map { d in
                            print("found \(d["firstName"]) id: \(d.documentID)") // Fixed an Optional 
                            return Person(id: d.documentID,
                                          firstName: d["firstName"] as? String ?? "",
                                          lastName: d["lastName"] as? String ?? "")
                        }
                    }
                }
                print(self.people)
            }
            else {
                print("Something went wrong")
            }
        }
    }

You might have to clean the build folder with Shift + Command + K and then Build your project again.
The only change I made was to get rid of the warning in this screenshot

in the print statement by adding a nil coalescing operator to d["firstName"] since the result is Optional. Like this:
print("found \(d["firstName"] ?? "") id: \(d.documentID)")

Let me know what happens after cleaning the Build folder.

I swear I’ve done multiple cleans and builds, but you telling me to do that again was the push xcode needed for that to work, thank you!

But I am having another issue that is now related to this, it seems like I am having a data race problem. My getData() func is in my ViewModel, and I have a view called Roster that I want to display a NavigationLink for each person in my Firestore. In Roster, I have:

struct Roster: View {
    private let model = ViewModel()
    
    var body: some View {
        
        
        VStack {
            NavigationStack{
               
                List {
                    ForEach(model.people){ person in
                        NavigationLink {
                            RosterDetail(Person: person)
                        } label: {
                            Text(person.firstName+" "+person.lastName)
                        }
                    }
                }
                 
            }
        }
    }
    init() {
        model.getData()
        print("roster init:\(model.people)")
    }
}


struct Roster_Previews: PreviewProvider {
    static var previews: some View {
        Roster()
    }
}

I have 3 documents in my Firestore collection right now. When the preview loads, the console shows the roster init print statement first with an empty array for people, then prints getData done with an empty array for people, then iterates through each of the 3 documents and prints them out. This is the exact opposite order I was expecting, and so my Roster view believes people is an empty array and does not display anything. How do I get the init to wait for getData to complete to display the NavigationList?

Screenshot 2023-06-08 at 11.47.02 AM

If I click my add button that randomly generates a new person and puts it in Firestore, I get a more expected log printing out the person array full of everyone in my Firestore, but even after that it does not display on the Roster view

@bRow

This could all be related to how you are injecting the ViewModel into your View hierarchy. If you are wanting to access the same ViewModel in different Views in your View hierarchy then you should inject that ViewModel into the Parent View using .enviromentObject(ViewModel()) and then in each view that requires access to that ViewModel use @EnvironmentObject var model: ViewModel.

This approach ensure that you only have one instance of the ViewModel that is shared across all views. It just makes the whole App much tidier.

The other thing is to use

.onAppear {
    model.getData()
}

rather than

    init() {
        model.getData()
        print("roster init:\(model.people)")
    }

since init() fires before the View appears.

I’m assuming that people is a @Published property in your ViewModel?