Home > front end >  DispatchGroup to wait for Firestore query completion
DispatchGroup to wait for Firestore query completion

Time:12-19

I am running multiple Firestore queries in a single user tapGesture, which requires me to ensure that there are minimum to no simultaneous Firestore queries running in the app. I have read multiple answers(enter image description here

With @Kiril S.'s answer, codes were corrected as shown below.

class WorkoutViewController: UIViewController {

let dispatchGroup = DispatchGroup()


alert.addAction(UIAlertAction(title: "Select", style: .default, handler: { [self]_ in
            
            dispatchGroup.enter()
            
            DispatchQueue.main.async {
                print("Firestore Sequence 2 Initiated")
                startActivityIndicator()
                
                if let user = Auth.auth().currentUser {
                    let userID = user.uid
                    
                    db.collection("user").whereField("author_uid", isEqualTo: userID).getDocuments { snapshot, error in
                        
                        if error == nil && snapshot != nil {
                            
                            for document in snapshot!.documents {
                            
                            let docID = document.documentID
                            
                            db.collection("user")
                                .document(docID)
                                .setData(["selectedWorkoutID" : workoutRow.workoutId], merge: true)
                                    
                            print("Firestore Sequence 2 Success - user selectedWorkoutID updated")
                                
                            }
                        }
                        
                        
                        if let user = Auth.auth().currentUser {
                            let userID = user.uid
                            checkAndCreateUserWorkoutProfile(selectedWorkout: workoutRow, userID: userID)
                         dispatchGroup.leave()
                        }
                    }
                }
            }

            dispatchGroup.notify(queue: .main) {
                print("Firestore Sequence 4 Initiated")
                print("Firestore Sequence 5 Initiated - Create/Read User Specific Dayprogram data")
                }

        }))

func checkAndCreateUserWorkoutProfile(selectedWorkout: Workout, userID: String) {

        print("Firestore Sequence 3 Initiated - createORread user specific workout profile")
        
        dispatchGroup.enter()
        
        db.collection("Workout")
            .whereField("workoutId", isEqualTo: selectedWorkout.workoutId)
            .whereField("userID", isEqualTo: userID)
            .getDocuments() { (querySnapshot, err) in
                
                if querySnapshot?.count == 0 {

                    var ref: DocumentReference? = nil
                  
                    ref = self.db.collection("Workout").addDocument(data:
                        [
                        "author_uid": selectedWorkout.author_uid!,
                        "workoutId": selectedWorkout.workoutId,
                        "userID": userID
                        ])

                     { err in
                        if let err = err {
                            print("Error adding user specific workout profile: \(err)")
                            self.dispatchGroup.leave()
                        } else {
                            print("Firestore Sequence 3 Success - User specific workout profile added/created with ID: \(ref!.documentID)")
                            self.dispatchGroup.leave()
                        }
                    }     
                }      
            }
        }

Xcode Console showing corrected sequence of codes/queries. enter image description here

CodePudding user response:

So basically what happens now:

  • Alert action starts
  • Creates let dispatchGroup = DispatchGroup()
  • Enters that group
  • Leaves the group ==> dispatchGroup.notify fires
  • checkAndCreateUserWorkoutProfile starts, creates its own dispatch group and so on (already wrong, doesn't matter)

So you have 2 problems:

  1. You have to have 1 dispatch group, not a separate group for each function. So move let dispatchGroup = DispatchGroup() to class member level, that way both functions are in the same dispatch group.

  2. And your checkAndCreateUserWorkoutProfile needs to enter dispatch group before previous call leaves it. So the order should be changed to

    if let user = Auth.auth().currentUser {
        let userID = user.uid
        checkAndCreateUserWorkoutProfile(selectedWorkout: workoutRow, userID: userID)
    }
    dispatchGroup.leave()
    

That way:

  • Alert action starts
  • Enters a group defined as a class member
  • Calls checkAndCreateUserWorkoutProfile, which enters the same dispatch group and starts its async call
  • Alert action leaves the group (but notification doesn't fire, since checkAndCreateUserWorkoutProfile didn't leave the group yet)
  • checkAndCreateUserWorkoutProfile leaves the group eventually ==> dispatchGroup.notify fires
  • Related