Search code examples
swiftfirebase-realtime-databaseconcurrencygrand-central-dispatch

Concurrency problem when retrieving data from Firebase to display it in a table view


I am currently trying to retrieve user data from my real time database (Firebase) in Swift and display the user names in a table view. I have the following function to retrieve all the users from the database which is located in the main class of my file (the variable users I append to has already been initiated earlier on as an empty string array).

func retrieveAllUsers(){

        //hold all users
        var userFullName = ""

        self.usersReference.observeSingleEvent(of: .value, with: {(snapshot) in
        // if marker ID has children it means this marker has already been created
            //go in User ID uid
            if snapshot.hasChildren() {
                // go through all user ids
                for child in snapshot.children {
                    //initialize a user name and first name
                    var userFirstName = ""
                    var userLastName = ""
                    let subSnap = child as! DataSnapshot
                    //get child key with format UserID: uid
                    let key = subSnap.key
                    // go inside User ID to retrieve name and first name of every user
                    DispatchQueue.main.async {
                        self.usersReference.child(key).observeSingleEvent(of: .value, with: { (snapshot2) in
                            // go through user parammters to only retrieve his name and first name
                            for grandChild in snapshot2.children {
                                let nameGatherer = grandChild as! DataSnapshot
                                if nameGatherer.key == "First Name:"{
                                    userFirstName = nameGatherer.value as! String
                                }
                                if nameGatherer.key == "Last Name:"{
                                    userLastName = nameGatherer.value as! String
                                }
                            }//end of loop inside user parameters
                            //add his full name to the list of users which we will then filter later on
                            userFullName = userFirstName + userLastName
                            self.users.append(userFullName)
                            self.tableView.reloadData()
                            print("IN CLOSURE: \(self.users)")
                        })// end of observation of one user parameters
                    }//end of dispatch async
                print("OUTSIDE CLOSURE1: \(self.users)")
                }//end of looking inside the user ID node
            print("OUTSIDE CLOSURE2: \(self.users)")
            }//end of if there are users children
        print("OUTSIDE CLOSURE3: \(self.users)")
        })//end of observation of users reference
        print("OUTSIDE CLOSURE4: \(self.users)")
    }

And in my viewDidLoad I have

retrieveAllUsers()
print("USERS:\(self.users)")
tableView.reloadData()

I have tried to use concurrency with Dispatchmain.async (it is my first time doing concurrency in my programs). Yet it does not seem to work since the print in my viewDidLoad appears first and users is empty hence it does not show up in my table view and the print ** print("OUTSIDE CLOSURE1: (self.users)")** comes last with the array containing the correct user information.

Could anyone help me to get the correct user information to be displayed in my table view? I'd really appreciate it. Many thanks! Anthony


Solution

  • Your problem is cause because your call to self.tableView.reloadData() is actually inside the Firebase callback with your data. This callback is not guaranteed to be on the main thread, and all UI operations must occur on the main thread, always. You need to wrap the call to reload the table inside of DispatchQueue.main.async directly (not nested inside of another call) to ensure it is called on the main thread.

    You also do not need to call the nested call inside DispatchQueue.main.async. See my example:

    func retrieveAllUsers(){
    
            //hold all users
            var userFullName = ""
    
            self.usersReference.observeSingleEvent(of: .value, with: {(snapshot) in
            // if marker ID has children it means this marker has already been created
                //go in User ID uid
                if snapshot.hasChildren() {
                    // go through all user ids
                    for child in snapshot.children {
                        //initialize a user name and first name
                        var userFirstName = ""
                        var userLastName = ""
                        let subSnap = child as! DataSnapshot
                        //get child key with format UserID: uid
                        let key = subSnap.key
                        // go inside User ID to retrieve name and first name of every user
    
                            self.usersReference.child(key).observeSingleEvent(of: .value, with: { (snapshot2) in
                                // go through user parammters to only retrieve his name and first name
                                for grandChild in snapshot2.children {
                                    let nameGatherer = grandChild as! DataSnapshot
                                    if nameGatherer.key == "First Name:"{
                                        userFirstName = nameGatherer.value as! String
                                    }
                                    if nameGatherer.key == "Last Name:"{
                                        userLastName = nameGatherer.value as! String
                                    }
                                }//end of loop inside user parameters
                                //add his full name to the list of users which we will then filter later on
                                userFullName = userFirstName + userLastName
                                self.users.append(userFullName)
                                DispatchQueue.main.async {
                                    self.tableView.reloadData()
                                }
                                print("IN CLOSURE: \(self.users)")
                            })// end of observation of one user parameters
    
                    print("OUTSIDE CLOSURE1: \(self.users)")
                    }//end of looking inside the user ID node
                print("OUTSIDE CLOSURE2: \(self.users)")
                }//end of if there are users children
            print("OUTSIDE CLOSURE3: \(self.users)")
            })//end of observation of users reference
            print("OUTSIDE CLOSURE4: \(self.users)")
        }