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

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.
.
.
.

Related

Firebase removeObserver is not working in Swift 5

I am using Firebase's Realtime database in my app. I am fetching data from the database and do some change and after that I am removing the observer which is not working fine.
I have some data in Realtime Database like this:
I am using firebase's observe(.value) function to get this value and after that I am updating an entry and then I am removing the observer. This is my code:
func updatePoints() {
let firebaseId = UserDefaults.standard.value(forKey: "firebaseId") as? String ?? ""
let reference = self.database.child("Points").child(firebaseId)
var handler : UInt = 0
handler = reference.observe(.value, with: { snapshot in
guard let userPoints = snapshot.value as? [String : Any] else {
print("no points data found")
return
}
let pointsLeft = userPoints["points_left"] as? Int ?? 0
reference.child("points_left").setValue(pointsLeft - 1)
reference.removeObserver(withHandle: handler)
})
}
The problem now is, this observer runs twice. For example, if "points_left" : 10, then after this function the points left will have 8 value but it should have 9 instead. It is running twice and I am not understanding why is it doing so as I am using removeObserver. Can someone help me with this?
The reason to the above unexpected behaviour is the setValue function you called to update the points is triggering another .value event in the database. Then it triggers the observer again. Therefore, by the time you remove the observer, it has already triggered twice. This leads to decrease of points by 2 instead of 1.
So if u interchange the last two lines, by the time you call the setValue function observer is removed. So it will not get triggered for the second time.

How to observe multiple Firebase entries in a loop and run completion block once every observe function has finished?

I'm trying to create a feed that should include posts made by people a user follows, and I'm using following function to get posts for user feed:
func observePosts(for userID: String, completion: #escaping ([UserPost], String) -> Void) {
let databaseKey = "Posts/\(userID)/"
databaseRef.child(databaseKey).queryOrderedByKey().queryLimited(toLast: 12).observe(.value) { snapshot in
var retrievedPosts = [UserPost]()
var lastSeenPostKey = ""
let dispatchGroup = DispatchGroup()
for snapshotChild in snapshot.children.reversed() {
guard let postSnapshot = snapshotChild as? DataSnapshot,
let postDictionary = postSnapshot.value as? [String: Any]
else {
print("Couldn't cast snapshot as DataSnapshot")
return
}
dispatchGroup.enter()
lastSeenPostKey = postSnapshot.key
do {
let jsonData = try JSONSerialization.data(withJSONObject: postDictionary as Any)
let decodedPost = try JSONDecoder().decode(UserPost.self, from: jsonData)
UserService.shared.observeUser(for: decodedPost.userID) { postAuthor in
decodedPost.author = postAuthor
retrievedPosts.append(decodedPost)
dispatchGroup.leave()
}
} catch {
print("Couldn't create UserPost from postDictionary")
}
}
dispatchGroup.notify(queue: .main) {
completion(retrievedPosts, lastSeenPostKey)
}
}
}
First step I'm getting user feed posts with .observe function and after that I'm iterating through every received post calling a UserService.shared.observeUser method that observes user data for every post to set post author's profile picture and username.
The problem with my method is that I'm using DispatchGroup's .enter and .leave methods to notify once every observe function got user data so that I could call completion block in which I'm reloading tableView.
Sure it works fine first time but since it's observe function it's called every time there's a change to user so once user updates his data (profile pic, usernama etc.) it crashes on dispatchGroup.leave() line since it's not balanced with dispatchGroup.enter()
What would be a correct approach to solve this issue?
I've been trying to solve this problem for 3 days searching all the internet, and only advice I found is to fetch it one time using observeSingleValue() instead of observing, but I really need to observe user profile data to keep profile pics and other necessary data up to date.

Multiple async request in nested for loops

I'm trying to get data from my database and after obtaining a piece of data, using that piece of data to find a new piece of data.
At the end, I can piece these together and return the derived data. I'm not sure this is the best way to approach this, but this is where im at as of now.
My problem is that each call to the database (Firebase) is async and therefore I need to somehow wait for the async to finish, before going on.
I've looked at dispatch group and heres what I have so far:
let taskGroup = DispatchGroup()
for buildingKey in building.allKeys
{
var aprt = NSDictionary()
taskGroup.enter()
// ASYNC REQUEST
getAbodesWithUID(UID: buildingKey as! String, callback: { (success, abodes) in
aprt = abodes
taskGroup.leave()
})
taskGroup.enter()
for key in aprt.allKeys
{
// ASYNC REQUEST
getTenantsWithAprt(UID: key as! String, callback: { (success, user) in
for userKey in user.allKeys
{
let dict = NSMutableDictionary()
dict.setValue(((building[buildingKey] as? NSDictionary)?["Address"] as? NSDictionary)?.allKeys[0] as? String, forKey: "Building")
dict.setValue((user[userKey] as? NSDictionary)?["Aprt"], forKey: "Number")
dict.setValue((user[userKey] as? NSDictionary)?["Name"], forKey: "Name")
dict.setValue(userKey, forKey: "UID")
dict.setValue((user[userKey] as? NSDictionary)?["PhoneNumber"], forKey: "Phone")
apartments.append(dict)
}
taskGroup.leave()
})
}
}
taskGroup.notify(queue: DispatchQueue.main, execute: {
print("DONE")
callback(true, apartments)
})
I can't seem to get it to callback properly
First, you should be iterating over aprt.allKeys inside of the callback for getAbodesWithUID, other wise, when the for loop executes aprt will be an empty dictionary.
Secondly, the taskGroup.enter() call above that for loop should be inside of the for loop, because it needs to be called once for every key. It should be placed where the // ASYNC REQUEST comment currently is.
This is precisely what "promises" are for is for. They are available in Swift via a 3rd party add-in. A popular way to do this is to push all your reads/gets into an array. Then you promise.all(yourArray) which returns the array of results/values that you then iterate over to get at each one.
From this other answer:
You can look into when which may provide what you need and is covered
here.
Use the loop to put your promises into an array and then do something
like this:
when(fulfilled: promiseArray).then { results in
// Do something
}.catch { error in
// Handle error
}

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.

Firebase Observe called after following command

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