Home > Enterprise >  Firebase function not getting called inside a forEach loop with a DispatchGroup
Firebase function not getting called inside a forEach loop with a DispatchGroup

Time:09-17

I apologize if this question is simple or the problem is obvious as I am still a beginner in programming.

I am looping over an array and trying to make an async Firestore call. I am using a DispatchGroup in order to wait for all iterations to complete before calling the completion.

However, the Firestore function is not even getting called. I tested with print statements and the result is the loop iterations over the array have gone through with an enter into the DispatchGroup each time and the wait is stuck.

func getUserGlobalPlays(username: String, fixtureIDs: [Int], completion: @escaping (Result<[UserPlays]?, Error>) -> Void) {    
    let chunkedArray = fixtureIDs.chunked(into: 10)
    var plays: [UserPlays] = []
    let group = DispatchGroup()
    
    chunkedArray.forEach { ids in
        
        group.enter()
        print("entered")
        DispatchQueue.global().async { [weak self] in
            self?.db.collection("Users").document("\(username)").collection("userPlays").whereField("fixtureID", in: ids).getDocuments { snapshot, error in
                guard let snapshot = snapshot, error == nil else {
                    completion(.failure(error!))
                    return
                }
                for document in snapshot.documents {
                    let fixtureDoc = document.data()
                    let fixtureIDx = fixtureDoc["fixtureID"] as! Int
                    let choice = fixtureDoc["userChoice"] as! Int
                    plays.append(UserPlays(fixtureID: fixtureIDx, userChoice: choice))
                }
                group.leave()
                print("leaving")
            }
        }
    }
    group.wait()
    print(plays.count)
    completion(.success(plays))
}

CodePudding user response:

There are a few things going on with your code I think you should fix. You were dangerously force-unwrapping document data which you should never do. You were spinning up a bunch of Dispatch queues to make the database calls in the background, which is unnecessary and potentially problematic. The database call itself is insignificant and doesn't need to be done in the background. The snapshot return, however, can be done in the background (which this code doesn't do, so you can add that if you wish). And I don't know how you want to handle errors here. If one document gets back an error, your code sends back an error. Is that how you want to handle it?

func getUserGlobalPlays(username: String,
                        fixtureIDs: [Int],
                        completion: @escaping (_result: Result<[UserPlays]?, Error>) -> Void) {
    let chunkedArray = fixtureIDs.chunked(into: 10)
    var plays: [UserPlays] = []
    let group = DispatchGroup()

    chunkedArray.forEach { id in
        group.enter()

        db.collection("Users").document("\(username)").collection("userPlays").whereField("fixtureID", in: id).getDocuments { snapshot, error in
            if let snapshot = snapshot {
                for doc in snapshot.documents {
                    if let fixtureIDx = doc.get("fixtureIDx") as? Int,
                       let choice = doc.get("choice") as? Int {
                        plays.append(UserPlays(fixtureID: fixtureIDx, userChoice: choice))
                    }
                }
            } else if let error = error {
                print(error)
                
                // There was an error getting this one document. Do you want to terminate
                // the entire function and pass back an error (through the completion
                // handler)? Or do you want to keep going and parse whatever data you can
                // parse?
            }
            group.leave()
        }
    }
    
    // This is the completion handler of the Dispatch Group.
    group.notify(queue: .main) {
        completion(.success(plays))
    }
}
  • Related