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(Waiting until the task finishes) on this issue, but my queries are not running in the desired Sequence as I intended to.
I would appreciate your help to guide me on using DispatchGroup to ensure logical sequence of codes & queries.
I want to ensure that the the Firestore query within function checkAndCreateUserWorkoutProfile is completed before the 2 print statements within DispatchGroup.notify(...), starting with "Firestore Sequence" method are initiated. Instead, the query completed after the two print statements were initiated.
Below is my code and screenshot of Xcode debugger. As shown in the screenshot, the problem is that using "DispatchGroup.notify(...)" is not waiting until the Firestore query within the function checkAndCreateUserWorkoutProfile is completed.
alert.addAction(UIAlertAction(title: "Select", style: .default, handler: { [self]_ in
let dispatchGroup = DispatchGroup()
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")
}
}
dispatchGroup.leave()
if let user = Auth.auth().currentUser {
let userID = user.uid
checkAndCreateUserWorkoutProfile(selectedWorkout: workoutRow, userID: userID)
}
}
}
}
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")
let dispatchGroup = DispatchGroup()
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)")
dispatchGroup.leave()
} else {
print("Firestore Sequence 3 Success - User specific workout profile added/created with ID: \(ref!.documentID)")
dispatchGroup.leave()
}
}
}
}
}
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()
}
}
}
}
}
So basically what happens now:
let dispatchGroup = DispatchGroup()
dispatchGroup.notify
firescheckAndCreateUserWorkoutProfile
starts, creates its own dispatch group and so on (already wrong, doesn't matter)So you have 2 problems:
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.
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:
checkAndCreateUserWorkoutProfile
, which enters the same dispatch group and starts its async callcheckAndCreateUserWorkoutProfile
didn't leave the group yet)checkAndCreateUserWorkoutProfile
leaves the group eventually ==> dispatchGroup.notify
fires