0

I have the following function which fetches data from Firebase, fills a compliments array and is suppose to pass the array back via the completion handler.

The array that is returned is always empty even though objects are added to it?

Have I placed the completion handler code in the wrong location, I have tried inserting within all the curly braces and nothing works.

Break point at handler(compliments, true) execution line outputs this:

0x0000000102bb3fe8 vipeeps`partial apply forwarder for closure #1 (Swift.Array<Any>, Swift.Bool) -> () in vipeeps.ConversationsListVC.loadNewInvitesData() -> () at ConversationsListVC.swift

Function:

    func getComplimentsReceived(forUserId forId: String, handler: @escaping (_ complimentPack: [[String : Any]] ,_ success: Bool) -> ()){

    var compliments = [[String : Any]]()//empty array to hold compliments

    REF_USERS_COMPLIMENTS.child(forId).observe(.value) { (snapshot) in


       for item in snapshot.children{


        let itemSnap = item as! DataSnapshot


        let dict = itemSnap.value as! [String : Bool]


        for (key, status) in dict {

            switch status {

            case true:
                //print(status)

                self.REF_USERS.child(snapshot.key).observeSingleEvent(of: .value, with: { (snapshot) in

                    let uid = snapshot.key
                    let name = snapshot.childSnapshot(forPath: "name").value as! String
                    let email = snapshot.childSnapshot(forPath: "email").value as! String
                    let profilePictureURL = snapshot.childSnapshot(forPath: "profilePictureURL").value as! String

                    let birthday = snapshot.childSnapshot(forPath: "birthday").value as! String
                    let firstName = snapshot.childSnapshot(forPath: "firstName").value as! String
                    let lastName = snapshot.childSnapshot(forPath: "lastName").value as! String
                    let gender = snapshot.childSnapshot(forPath: "gender").value as! String
                    let discoverable = snapshot.childSnapshot(forPath: "discoverable").value as! Bool
                    let online = snapshot.childSnapshot(forPath: "online").value as! Bool

                    let discoveryPrefs = snapshot.childSnapshot(forPath: "discoveryPrefs").value as! [String : Any]

                    let dictionary: [String : Any] = ["uid": uid, "name": name, "email": email, "profilePictureURL": profilePictureURL, "birthday": birthday, "firstName": firstName, "lastName": lastName, "gender": gender, "discoverable": discoverable, "online": online, "discoveryPrefs": discoveryPrefs]

                    let user = User(uid: uid, dictionary: dictionary)

                    let complimentPack = [
                        "type": "compliment",
                        "complimentId": key,
                        "active": status,
                        "fromUser": user
                        ] as [String : Any]

                    compliments.append(complimentPack)

                    print("compliments.count: \(compliments.count)")


                })//end observer

            case false:
                print(status)

                break

            }//end switch

         }//end for dict

       }//end for snapshot.item

      handler(compliments, true)

    }//end observe

 }//end func

Data structure:

enter image description here

9
  • 2
    This code won't work as expected anyway because you call the final handler(..) statement before the inner (asynchronous) closures are called. By the way: Why do you declare compliments and the array parameter as unspecified [Any] although you clearly know that's more specified [[String:Any]]?? And the Swift 3+ syntax for a closure is @escaping ([Any] , Bool) -> () without underscores and parameter labels. Commented Jul 16, 2018 at 6:51
  • call the handler from inside 'end observe' scope not inside 'end for snapshot.item' scope and try ! Commented Jul 16, 2018 at 6:54
  • thanks @vadian I have made the changes you suggested. Commented Jul 16, 2018 at 7:28
  • thanks @vivekDas tried that but still no luck :( I have edited my question with the latests code. Commented Jul 16, 2018 at 7:29
  • 2
    observeSingleEvent works asynchronously. The loop is executed instantly and handler(..) is called a long time (in terms of computer speed) before the first closure passes its snapshot. Commented Jul 16, 2018 at 8:19

1 Answer 1

1

You might want to use dispatch groups, which will be notfied once all your async observeSingelEvent calls have been finished:

  • In your observe code, you create a new DispatchGroup
  • Before calling the async observeSingleEvent, you enter the group.
  • At the end of the asyc closure, you leave the group
  • Once all enters and leave calls match, notify will be called, which then calls the handler

See here:

func getComplimentsReceived(forUserId forId: String, handler: @escaping (_ complimentPack: [Any] ,_ success: Bool) -> ()){
    REF_USERS_COMPLIMENTS.child(forId).observe(.value) { (snapshot) in
        var compliments = [Any]()//empty array to hold compliments
        let dispatchGroup = DispatchGroup()
        for item in snapshot.children {

            for (key, status) in dict {

                switch status {
                    case true:
                        dispatchGroup.enter()
                        self.REF_USERS.child(snapshot.key).observeSingleEvent(of: .value, with: { (snapshot) in
                            // ...
                            compliments.append(complimentPack)
                            dispatchGroup.leave()
                    })//end observer
                    case false:
                        print(status)
                        break

                }//end switch

            }//end for dict

        } //end for snapshot.item

        dispatchGroup.notify(queue: .main) {
            handler(compliments, true)
        }
    } //end observe

}//end func

Addition Edit func (by @Roggie):

func loadNewInvitesData(){


    guard let uid = Auth.auth().currentUser?.uid else { return }

    DataService.run.getInvitesAndCompliments(forUserId: uid) { (array, sucess) in


            for item in array {
                let user = item ["fromUser"] as! User
                let uid = user.uid
                print("user id: \(uid)")
                DataService.run.observeUser(forUserId: uid, handler: { (user) in
                    self.users.append(user)
                })
            }
        self.invites = array
        self.collectionView.reloadData()

    }

}//end func
Sign up to request clarification or add additional context in comments.

5 Comments

thanks for your suggestion, I will try this and report back.
yes - this worked - thanks so much for your assistance to you and @vadian
I know this dates back a little, but I've since had a requirement to self.REF_USERS.child(snapshot.key).observe instead of observeSingleEvent but since it executes the closure again when a change happens on Firebase there is a crash as I believe the enter and leave are off. If I remove all dispatchGroup code then not all nodes are read. What do suggest I could do?
If you want to use observe instead of observeSingleEvent, you'll have to rethink your program logic. As you already found out, the first will be called any time a modification occurs. So, you can only try to update the UI here (add/remove/delete), but not try to return an array of "all" the objects. Maybe you could have observe additionally to the existing observeSingleEvent, and just do the update stuff in there (e.g. without any dispatchGroup involved).
thanks for the feedback, the function above returns an Dictionary array compliments which includes the fetched User uid, I then attempted to loop through the uid and add the observe users to a separate users array for UI display purposes but I found got an index out of bound error when I reloaded the CollectionView...see additional edit above.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.