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
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)")
}