Setting collection view methods only after Firebase call is complete - ios

I'm attempting to set the numberOfItemsInSection method of my collection view with the count of a certain dictionary. The dictionary is set from a Firebase call (code below if that part matters). I was under the impression that Firebase calls were asynchronous anyway, and wouldn't need to be combined with a dispatch queue, closure, or separate completion handler.
However, when I try to set numberOfItemsInSection to return avatarDictionary.count, it's empty, and indeed printing that count shows 0. The dictionary in question does get set with its values (printing confirms that), but it needs to loop through all the users I'm fetching data for before it has all its values. I think when numberOfItemsInSection checks its return, the dictionary is still at 0.
Is that what's happening? If so, what's the best way to make sure the dictionary is fully set with all its values before setting the collection view?
Code:
func getParticipantInfo() {
let databaseRef = FIRDatabase.database().reference()
// Query Firebase users with those UIDs & grab their gender, profilePicture, and name
databaseRef.child("groups").child(currentRoomID).child("participants").observeSingleEvent(of: .value, with: { (snapshot) in
if let snapDict = snapshot.value as? [String:AnyObject] {
for each in snapDict {
let uid = each.key
let avatar = each.value["profilePicture"] as! String
let gender = each.value["gender"] as! String
let handle = each.value["handle"] as! String
let name = each.value["name"] as! String
// Set those to the dictionaries [UID : value]
self.avatarDictionary.setValue(avatar, forKey: uid)
self.nameDictionary.setValue(name, forKey: uid)
self.genderDictionary.setValue(gender, forKey: uid)
self.handleDictionary.setValue(handle, forKey: uid)
print("\n\navatarDictionary:\n \(self.avatarDictionary)")
print("\nhandleDictionary:\n \(self.handleDictionary)")
print("\ngenderDictionary:\n \(self.genderDictionary)")
print("\nnameDictionary:\n \(self.nameDictionary)")
}
}
}, withCancel: {(Err) in
print(Err.localizedDescription)
})
}

Use this after setting the array
collectionView.reloadData()
(Right after the for loop)

Related

Fetch first key from firebase database with swift 4 using ChildAdded

I'm trying to fetch the first key from my firebase database but for some reason nothing is being printed out. How can I get the first key from my firebase database using .childAdded
let userMessagesRef = Database.database().reference().child("user-message").child(uid).child(userId)
userMessagesRef.observe(.childAdded, with: { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
print(first)
This in incredibly easy if you literally are asking how to only ever get the first child of a node. Here's how to only get the first child of a /users node
func getFirstChild() {
let usersRef = self.ref.child("users")
usersRef.observeSingleEvent(of: .childAdded, with: { snapshot in
print(snapshot)
})
}
or
print(snapshot.key)
if you just want the key.
Or, if you want to use a query to do the same thing
func getFirstChildAgain() {
let usersRef = self.ref.child("users")
let query = usersRef.queryOrderedByKey().queryLimited(toFirst: 1)
query.observeSingleEvent(of: .value, with: { snapshot in
print(snapshot)
})
}
The child_added event is typically used when retrieving a list of items from the database. Unlike value which returns the entire contents of the location, child_added is triggered once for each existing child and then again every time a new child is added to the specified path. The event callback is passed a snapshot containing the new child's data. For ordering purposes, it is also passed a second argument containing the key of the previous child.
From: Read and Write Data on iOS
Per your requirements, this is possible in .value and childAdded.
var child_array = [String:String]
let userMessagesRef = Database.database().reference().child("user-message").child(uid).child(userId)
userMessagesRef.observe(.childAdded, with: { (snapshot) in
let value = snapshot.value as? String ?? "Empty String"
let key = snapshot.key
child_array[key] = value;
}) { (error) in
print(error.localizedDescription)
}
then:
if let first = child_array.first?.key {
print(first) // First Key
}
Big NOTE: child_added randomly collects this data, you should never use it to sort your data

Manipulating data after retrieved it from Firebase

Here's my code:
//Get data from Firebase
func getData(withBlock completion:#escaping() ->Void){
let ref = Database.database().reference().child("hobbies")
let query = ref.queryOrdered(byChild: "cost").queryEqual(toValue: "low")
query.observe(.childAdded, with: {(snapshot) in
self.user_choice_Cost.append((snapshot.childSnapshot(forPath: "hobbyName").value as? String)!)
completion()
//print(self.user_choice_Cost)
})
{ (error) in
print(error)
}
//Manipulating data
getData{
let set2:Set<String> = ["a"]
let set1:Set<String> = Set(self.user_choice_Cost)
print(set1.union(set2))}
This works correctly! But is there any way I can get the user_choice_Cost with all value(["a","b"]) instead of one by one(["a"],["a","b")] and manipulate user_choice_Cost array without putting it inside inside getData{}. Because if I put that outside it will return only "a"
When you observe .childAdded, your completion handler gets called for each individual child that matches your query. If you want to get called only once for all matching children, you should observe .value:
query.observe(.value, with: {(snapshot) in
Since your completion handler gets called only once, the snapshot in this case contains all matching nodes. So you need to loop over snapshot.children:
query.observe(.value, with: {(snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
let value: String = (child.childSnapshot(forPath: "hobbyName").value as? String)!;
user_choice_Cost.append(value)
}
print("value \(user_choice_Cost)")
})
With this code you'll only see one logging output, with all matching hobby names.

(Firebase) printing wrong number of users in database when counting users in array

I am retrieving a snapshot of all of the users in my Firebase database. After I append all of my users from my "users" node of my database to an array of users , I attempt to print the user count (which should be 12) and it says it is 0 instead.
I added a breakpoint at the closing brace of the if-statement which shows to me that the user count reaches 12. However, by the time it comes time to print this number, it is zero. Stepping through a bunch of times is just a lot of assembly code that tells me nothing.
I call this function in the viewDidLoad() method and tried to print the users count after this function is called in addition to printing it within the function and it is still 0.
func getUsers() {
let ref = Database.database().reference().child("users")
ref.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User()
user.id = snapshot.key
user.setValuesForKeys(dictionary)
self.users.append(user)
}
}, withCancel: nil)
print(users.count)
}
Here you go, a fix below. Like Ahmed is saying: it takes some time to get the data since it is async.
func getUsers(completionHandler:#escaping (Int) -> ()){
let ref = Database.database().reference().child("users")
ref.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User()
user.id = snapshot.key
user.setValuesForKeys(dictionary)
self.users.append(user)
completionHandler(self.users.count)
}
})
override func viewDidLoad() {
super.viewDidLoad()
getUsers(){ (count) in
print(count)
}
}
It is pretty straightforward what is going on, I just added a completion handler.
Because observe work asynchronously, and you are printing users.count before updating users array...

How to save Firebase key for Swift in TableViewcell

I am a beginner. The main function is that when user click a each button of cell, it will update data. But I just write a new data, it cannot update existing data.
ViewController.swift
This function write's a new key is not save existing key. How can I solve?
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("location").queryOrderedByKey().observe(.childAdded, with: {snapshot in
guard let firebaseResponse = snapshot.value as? [String:Any] else
{
print("Snapshot is nil hence no data returned")
return
}
let status = firebaseResponse["status"] as! Int
let key = databaseRef.childByAutoId().key
let up = ["id":key,
"status":status] as [String : Any]
databaseRef.child("location").child(key).setValue(up)
self.posts.insert(postStruct(status:status,key:key), at: 0)
self.tableView.reloadData()
})
This my Exercise xcode in google drive
https://drive.google.com/open?id=0B2jj6V1tOGcVTm9JSy1uNV9fZnM
This is firebase data for image:
Problem is in this line
let key = databaseRef.childByAutoId().key
You are creating new autoID instead of using the current child autoId from snapshot.key, so change that line with below one and you all set to go.
let key = snapshot.key

Database observer function not acting as observer in viewDidLoad, only viewDidAppear

I have a function that is in charge of observing the database. It sets some dictionaries upon first load, and then keeps an eye on changes thereafter and updates those dictionaries accordingly. When called in viewDidAppear, this works perfectly. But if I move it to viewDidLoad, it sets the values initially, but doesn't "observe" - in other words if I change one of the values, for example status, that change is not reflected until I leave the view and come back.
I need to have it in viewDidLoad for other reasons - why exactly is it only properly working as an observer if it's in viewDidAppear, and is there anything I can change to make it work as an observer in viewDidLoad?
This is the function:
func getParticipantInfo() {
let databaseRef = FIRDatabase.database().reference()
let groupRef = databaseRef.child("groups").child(currentRoomIdGlobal)
groupRef.observe(.childAdded, with: { snapshot in
if let snapDict = snapshot.value as? [String : AnyObject] {
for each in snapDict {
let uid = each.key
let avatar = each.value["profilePicture"] as! String
let gender = each.value["gender"] as! String
let handle = each.value["handle"] as! String
let name = each.value["name"] as! String
let status = each.value["status"] as! String
// Set those to the dictionaries [UID : value]
self.avatarDictionary.setValue(avatar, forKey: uid)
self.nameDictionary.setValue(name, forKey: uid)
self.genderDictionary.setValue(gender, forKey: uid)
self.handleDictionary.setValue(handle, forKey: uid)
self.statusDictionary.setValue(status, forKey: uid)
DispatchQueue.main.async {
// Nav bar
self.navCollectionView?.collectionView?.reloadData()
}
}
}
})
}
One reason I can see is that you are observing only .childAdded events. You'll have to subscribe to all the changes in order to trigger the event. try subscribing to .value event which will listen to all the changes on Database and reflect in app.
As far as viewDidLoad and viewDidAppear is concerned, make sure that you have setup all the class variables before attaching a subscriber to Firebase. viewDidLoad or viewDidAppear doesn't matter as far as the setup is right.

Resources