Firebase Observe called after following command - swift

I am trying to load a username form a Firebase which is than supposed to be set in an Object. But the Firebase Observe Command is getting called after the name already gets set. What is the problem and how can I fix it?
let ref = Database.database().reference().child("Users").child(currentMessage.senderId).child("name")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
// This is supposed to be called first
self.username = snapshot.value as! String
print(self.username)
})
// This somehow gets called first
let nameModel = NameModel(name: self.username, uid: *some UID*)
decoratedItems.append(DecoratedChatItem(chatItem: nameModel, decorationAttributes: nil))

Firebase loads data from its database asynchronously. This means that the code executes in a different order from what you may expect. The easiest way to see this is with some log statements:
let ref = Database.database().reference().child("Users").child(currentMessage.senderId).child("name")
print("Before attaching observer")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
print("In completion handler")
})
print("After attaching observer")
Unlike what you may expect, this code prints:
Before attaching observer
After attaching observer
In completion handler
This happens because loading data from Firebase (or any other web service) may take some time. If the code would wait, it would be keeping your user from interacting with your application. So instead, Firebase loads the data in the background, and lets your code continue. Then when the data is available, it calls your completion handler.
The easiest way to get used to this paradigm is to reframe your problems. Instead of saying "first load the data, then add it to the list", frame your problem as "start loading the data. when the data is loaded, add it to the list".
In code this means that you move any code that needs the data into the completion handler:
let ref = Database.database().reference().child("Users").child(currentMessage.senderId).child("name")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
self.username = snapshot.value as! String
let nameModel = NameModel(name: self.username, uid: *some UID*)
decoratedItems.append(DecoratedChatItem(chatItem: nameModel, decorationAttributes: nil))
})
For some more questions on this topic, see:
Swift: Wait for Firebase to load before return a function
Can Swift return value from an async Void-returning block?
Array items are deleted after exiting 'while' loop?
Asynchronous functions and Firebase with Swift 3

Related

How to read values of a child from Firebase Realtime DB?

I am trying to get values from my database. However, I am unsuccessful. Here is what the DB looks like:
Here is what I tried to do, to read the values:
taskRef = Database.database().reference(withPath: "Tasks").child(titleOfTask)
taskRef?.observeSingleEvent(of: .childAdded, with: { (snapshot) in
if let taskDict = snapshot.value as? [String:Any] {
print("Printing ---------> ",taskDict["Description"] as Any)
}
})
but nothing happens. Ideally, I want to read both the description in a different variable and due date into a different variable.
Important Things to know:
"Tasks" is.. you can think of it as a table name.
Alpha, Bravo, Jalabi... etc are all "Task Names" or childs of "Tasks"
Description, Due Date are the values
You're attaching a listener to /Tasks/$titleOfTask in your JSON, say /Tasks/Alpha. That listener is asking to be called for the first child node of /Tasks/Alpha, so the snapshot in your code will point to /Tasks/Alpha/Description. When you then print taskDict["Description"], you are printing the value of /Tasks/Alpha/Description/Description, which does not exist.
The simplest fix is to listen for the .value event:
taskRef?.observeSingleEvent(of: .value, with: { (snapshot) in
if let taskDict = snapshot.value as? [String:Any] {
print("Printing ---------> ",taskDict["Description"] as Any)
}
})
Now the snapshot has all data from /Tasks/Alpha, and thus the taskDict["Description"] ends up being the value from /Tasks/Alpha/Description.

Unable to access variable inside closure swift

I'm trying to get my firebase database data into a variable to use it in my project
var someArray = [Array]()
let dbRef = Database.database().reference().child("SomeDatabase")
func loadSomeDatabaseData {
dbRef.observeSingleEvent(of: .value) { (snapshot) in
let someDict = snapshot.value as! [String:Any]
let keysOfSomeDict = Array(someDict.keys)
self.someArray.append(contentsOf: keysOfSomeDict)
self.collectionView?.reloadData()
}
}
I've tried calling loadSomeDatabaseData() in my viewDidload, followed by printing someArray, which results in an empty array. I know the keysOfSomeDict array has the correct data that I want, since i tried printing this array directly inside the closure. I would however also like to be able to print and use this data elsewhere in my app.
The Firebase observeSingleEvent method is asynchronous method. it executive in background only because it take time to fetch data from Firebase.
if you print array immediately means you get only empty array.
so print array once you get the data from Firebase. for that you can use escaping closure
Function Declaration:
func loadSomeDatabaseData(resultArray : #escaping([Array])->()) {
dbRef.observeSingleEvent(of: .value) { (snapshot) in
let someDict = snapshot.value as! [String:Any]
let keysOfSomeDict = Array(someDict.keys)
self.someArray.append(contentsOf: keysOfSomeDict)
resultArray(self.someArray)
}
}
Func Call:
self.loadSomeDatabaseData{(firebaseReposne) in
print("FirebaseData" , firebaseReposne) // Hope here you will get your firebase data.
self.collectionView?.reloadData()
}
The observeSingleEvent is asynchronous. So immediately printing someArray after calling loadSomeDatabaseData will result in an empty array. It takes sometime to retrieve the data from Firebase api.
To use this data elsewhere in the app, you could set a flag indicating the data is loaded or send a notification to inform the data is available.

Swift 5, how to execute code after fetching all childadded from Firebase

How can I execute some code after fetching all childadded from Firebase in Swift 5?
I've tried using DispatchGroup and observe .value, but none of them worked efficiently.
let dispatchGroup = DispachGroup()
ref.child("path").observe(.childAdded, with: { (snapshot) in
self.dispatchGroup.enter()
//store snapshot data into an object
self.dispatchGroup.leave()
})
dispatchGroup.notify(queue: .main) {
//code to execute after all children are fetched
}
In this case, the code will be executed before fetching the data.
How can I execute code when only the callback block reaches the last child?
One option is to leverage that Firebase .value functions are called after .childAdded functions.
What this means is that .childAdded will iterate over all childNodes and then after the last childNode is read, any .value functions will be called.
Suppose we want to iterate over all users in a users node, print their name and after the last user name is printed, output a message that all users were read in.
Starting with a simple structure
users
uid_0
name: "Jim"
uid_1
name: "Spock"
uid_2
name: "Bones"
and then the code that reads the users in, one at a time, prints their name and then outputs to console when all names have been read
var initialRead = true
func readTheUsers() {
let usersRef = self.ref.child("users")
usersRef.observe(.childAdded, with: { snapshot in
let userName = snapshot.childSnapshot(forPath: "name").value as? String ?? "no name"
print(userName)
if self.initialRead == false {
print("a new user was added")
}
})
usersRef.observeSingleEvent(of: .value, with: { snapshot in
print("--inital load has completed and the last user was read--")
self.initialRead = false
})
}
and the output
Jim
Spock
Bones
--inital load has completed and the last user was read--
Note this will leave an observer on the users node so if a new user is added it will print their name as well.
Note Note: self.ref points to my root firebase reference.
When you're listening to .childAdded, there is no way when your code is getting called for the last child. So if you must treat the last child different, listen for .value and loop over the children as shown here.

How do I retrieve only the autoids since I last called a function from firebase using swift.

My firebase database is structured as:
events
autoid
event name: ""
event date: ""
autoid
event name: ""
event date: ""
I currently have a function that returns all of the autoids from the events node then writes them to an array so I can use them in another snapshot.
The first time the function runs, it works as expected. But if I leave the view and come back it crashes. Which I think is because it's trying to append the array again, duplicating the values.
Here's my function
func getEvents() {
self.dispatchGroup.enter()
Database.database().reference().child("Events").observe(DataEventType.value, with: { (snapshot) in
if let dictionary = snapshot.children.allObjects as? [DataSnapshot] {
// self.dispatchGroup.enter()
for child in dictionary {
let eventid = child.key
self.eventsArray.append(eventid)
// print(eventid)
// print(self.eventsArray)
}
self.dispatchGroup.leave()
print(self.eventsArray)
}
})
}
Wondering how I can retrieve the existing autoids and any new ones that have been added when I return to the view. I tried .childadded but it returns event name, event date etc and I need the autoid.
I'm new to firebase and swift so any tips or recommendations are welcomed!
If you want to first handle the initial data and then get notified of only the new data, you're typically looking for the .childAdded event.
Database.database().reference().child("Events").observe(DataEventType.childAdded, with: { (snapshot) in
let eventid = snapshot.key
print(eventid)
self.eventsArray.append(eventid)
self.dispatchGroup.leave()
print(self.eventsArray)
}
When you first run this code, the .childAdded event fires for each existing child node. And after that, it fires whenever a new child is added. Similarly, you can listen for .childChanged and .childRemoved events to handle those.

How can my code know when Firebase has finished retrieving data?

I just want to ask about firebase retrieve data. How can i handle firebase retrieve data finished? I don't see any completion handler.
I want to call some function after this firebase data retrieve finished. How can i handle???
DataService.ds.POST_REF.queryOrderedByChild("created_at").observeEventType(.ChildAdded, withBlock: { snapshot in
if let postDict = snapshot.value as? Dictionary<String, AnyObject> {
let postKey = snapshot.key
let post = Post(postKey: postKey, dictionary: postDict)
self.posts.append(post)
}
})
In Firebase, there isn't really a concept of 'finished' (when listening to 'child added'). It is just a stream of data (imagine someone adds a new record before the initial data is 'finished'). You can use the 'value' event to get an entire object, but that won't give you new records as they're added like 'child added' does.
If, you really need to use child added and get notified when it's probably finished, you can set a timer. I don't know swift, but here's the logic.
Set up your 'child added' event.
Set a timer to call some finishedLoading() function in 500ms.
Each time the 'child added' event is triggered, destroy the timer set in step two and create another one (that is, extend it another 500ms).
When new data stops coming in, the timer will stop being extended and finsihedLoading() will be called 500ms later.
500ms is just a made up number, use whatever suits.
Do one request for SingleEventOfType(.Value). This will give you all info initially in one shot, allowing you to then do whatever function you want to complete once you have that data.
You can create a separate query for childAdded and then do anything there you want to do when a new post has been added
Write your entire block of code in a function which has a completion handler like so:
func aMethod(completion: (Bool) -> ()){
DataService.ds.POST_REF.queryOrderedByChild("created_at").observeEventType(.ChildAdded, withBlock: { snapshot in
if let postDict = snapshot.value as? Dictionary<String, AnyObject> {
let postKey = snapshot.key
let post = Post(postKey: postKey, dictionary: postDict)
self.posts.append(post)
}
completion(true)
})
}
Then call it somewhere like so:
aMethod { success in
guard success == true else {
//Do something if some error occured while retreiving data from firebase
return
}
//Do something if everything went well.
.
.
.