Home > Enterprise >  SwiftUI Firestore Wait for Loading Data
SwiftUI Firestore Wait for Loading Data

Time:11-23

I am having trouble retrieving Firestore getdocument data before the view loads. I know that it returning values from multiple checks and there is probably a problem with how I handle the asynchronous functions.

My three variables that I am trying to set.

@Published var numParticipants = 0
@Published var totalAnswered = 0
@Published var showResults = false

This is the first function that gets and sets the number of participants variable.

func getRoom(roomId: String, onSuccess: @escaping(_ room: Room) -> Void) {
    DB.collection("Rooms").document(roomId).addSnapshotListener { document, error in
        DispatchQueue.main.async {
            if let dict = document?.data() {
                guard let decodeRoom = try? Room.init(fromDictionary: dict) else { return }
                onSuccess(decodeRoom)
            }
        }
    }
}

This the second function that gets and sets the total answered variable

func getNumParticipants(roomId: String, onSuccess: @escaping(_ numParticipants: Int) -> Void) {

    DB.collection("RoomsParticipants").document(roomId).collection("Participants").getDocuments { snapshot, error in
        DispatchQueue.main.async {
            if let error = error {
                print(error.localizedDescription)
                return
            } else {
                onSuccess(snapshot!.count)
            }
        }
    }
}

And then I use this last function to compare the two variables and load the view if they are equal, otherwise just wait until they are equal.

func checkShowResults(roomId: String) {
    isLoading = true
    
    self.getNumParticipants(roomId: roomId) { numParticipants in
        print("Number of docs: \(numParticipants)")
        DispatchQueue.main.async {
            self.numParticipants = numParticipants
        }
    }
    
    self.getRoom(roomId: roomId) { room in
        print("Total answered: \(room.totalAnswered)")
        DispatchQueue.main.async {
            self.totalAnswered = room.totalAnswered
            if self.totalAnswered == self.numParticipants {
                self.showResults = true
            }
        }
    }
    
    isLoading = false
}

Here is the Results View that I am trying to display based off the fetched data.

struct ResultsView: View {

@StateObject var resultsViewModel = ResultsViewModel()
var roomId: String

var body: some View {
    VStack {
        if !resultsViewModel.showResults {
                VStack {
                    ProgressView()
                    Text("Waiting for all participants \nto finish answering...")
                }
            } else {
                ShowResultsView()
                }
            }
        }
    }.navigationBarHidden(true)
    .onAppear {
        resultsViewModel.checkShowResults(roomId: roomId)
    }
}

Even if the totalAnswered and numParticipants are equal when the view initially displayed, showResults is always set to false. But when the data changes it eventually gets set to true if they become equal again. I think this is because the API call to firebase/firestore is taking time and the variables aren't set before the view loads. I don't really want to use async/await.

CodePudding user response:

Currently your code execute self.getNumParticipants(..) independently of self.getRoom(roomId: roomId). However in checkShowResults, self.getRoom(roomId: roomId) depends on self.numParticipants that you get from self.getNumParticipants(..). So you could try nesting your functions calls. Something like the following code:

func checkShowResults(roomId: String) {
    self.isLoading = true
    
    self.getNumParticipants(roomId: roomId) { numParticipants in
        print("Number of docs: \(numParticipants)")
        DispatchQueue.main.async {
            self.numParticipants = numParticipants
            
            // get the room after the numParticipants has been set
            self.getRoom(roomId: roomId) { room in
                print("Total answered: \(room.totalAnswered)")
                DispatchQueue.main.async {
                    self.totalAnswered = room.totalAnswered
                    if self.totalAnswered == self.numParticipants {
                        self.showResults = true
                        self.isLoading = false
                    }
                }
            }
            
        }
    }
  • Related