When I start the observer on a Firebase database node, I notice that Firebase continues to call the method observer even when there is no data change.
Here is my setup:
FIRDatabase
.database()
.reference(withPath: "test")
.observe(FIRDataEventType.value, with: { (snapshot) in
print("Firebase Data Updated");
}
);
When I make one change to the Firebase database, the observer calls its closure function more than one time.
Firebase Data Updated
Firebase Data Updated
Firebase Data Updated
Firebase Data Updated
Firebase Data Updated
Firebase Data Updated
Firebase Data Updated
...
Why does this occur?
How can I stop this from occurring and get only one call to the observer after an update?
It's likely this observer is being registered multiple times. When the user logs out, the listener block that you registered stays registered, such that when the user logs in again, you are registering a second listener.
It is often good practice to capture the ref and handle of observers, and remove the handles once you're done with them (i.e. when a user logs out). You can do so as such:
ref, handle = FIRDatabase
.database()
.reference(withPath: "test")
.observe(FIRDataEventType.value, with: { (snapshot) in
print("Firebase Data Updated");
}
);
And at sign out:
ref.removeObserverWithHandle(handle)
Otherwise another possible solution to ensure it is only called once is to use .observeSingleEvent() instead of .observe().
Swift 4:
If you want to remove all listeners registered with test node.
Database.database().reference(withPath: "test").removeAllObservers()
If you want to remove particular listener then you need particular handler for observer. You can use following sample code.
let ref = Database.database().reference(withPath: "test")
let handler = ref.observe(.value) { (snapshot) in
}
ref.removeObserver(withHandle: handler)
Related
I am testing my app where I have included a tableview that shows items from a Firebase Realtime database.
Every time I create a new object from the app to Firebase, the tableview updates and include the new added object.
But I have detected that sometimes it doesn't maintain the items updated.
For example, if I add a new item from inside the app, it is automically included in the tableView.
If another user creates a new item from inside his app, it is also included and updated in both apps.
But I have detected that not all items are included in the tableview and if I edit an item directly in the firebase console,the app doesn't update inmediatelly the item in the tableview.
Here is the code I am using to populate the tableview:
databaseRef.child(codigo_chat).queryOrderedByKey().observe(.childAdded, with: { (snapshot) in
if let valueDictionary = snapshot.value as? [AnyHashable:String]
{
let texto_mensaje = valueDictionary["mensaje"]
let codigo_chat = valueDictionary["codigo_chat"]
let datetime = valueDictionary["datetime"]
let emisor = valueDictionary["emisor"]
let receptor = valueDictionary["receptor"]
self.mensajesSorted.insert(MisMensajes(codigo_chat: codigo_chat!, datetime: datetime!,emisor: emisor!, mensaje: texto_mensaje!, receptor: receptor! ), at: 0)
self.chatTV.reloadData()
}
})
Right now you're only observing the .childAdded event, which (as its name implies) only fires when a child is added to the location (and initially for all existing children).
If you also want to get called when a child is modified, you should also observe the .childChanged event. In that callback you can then update the existing UI for that child.
In the save vein there are two more events you may want to respond to:
.childRemoved, which is fired when a child is removed from the database, and you'll want to remove it from the UI.
.childMoved, which is fired when the child is moved to a different location in the query results, and which typically goes hand-in-hand with a .childChanged event. In your .childMoved code, you'll usually move the UI element for the child to its new location.
Also see the Firebase documentation on listening for child events.
You need to call reloadData in the main queue.
DispatchQueue.main.async {
self.chatTV.reloadData()
}
Every firebase client example I see in Swift seems to oversimplify properly loading data from Firebase, and I've now looked through all the docs and a ton of code. I do admit that my application may be a bit of an edge case.
I have a situation where every time a view controller is loaded, I want to auto-post a message to the room "hey im here!" and additionally load what's on the server by a typical observation call.
I would think the flow would be:
1. View controller loads
2. Auto-post to room
3. Observe childAdded
Obviously the calls are asynchronous so there's no guarantee the order of things happening. I tried to simplify things by using a complete handler to wait for the autopost to come back but that loads the auto-posted message twice into my tableview.
AutoPoster.sayHi(self.host) { (error) in
let messageQuery = self.messageRef.queryLimited(toLast:25).queryOrdered(byChild: "sentAt")
self.newMessageRefHandle = messageQuery.observe(.childAdded, with: { (snapshot) in
if let dict = snapshot.value as? [String: AnyObject] {
DispatchQueue.main.async {
let m = Message(dict, key: snapshot.key)
if m.mediaType == "text" {
self.messages.append(m)
}
self.collectionView.reloadData()
}
}
})
}
Worth noting that this seems very inefficient for an initial load. I fixed that by using a trick with a timer that will basically only allow the collection view to reload maximum every .25s and will restart the timer every time new data comes in. A bit hacky but I guess the benefits of firebase justify the hack.
I've also tried to observe the value event once for an initial load and then only after that observe childAdded but I think that has issues as well since childAdded is called regardless.
While I'm tempted to post code for all of the loading methods I have tried (and happy to update the question with it), I'd rather not debug what seems to not be working and instead have someone help outline the recommended flow for a situation like this. Again, the goal is simply to auto-post to the room that I joined in the conversation, then load the initial data (my auto-post should be the most recent message), and then listen for incoming new messages.
Instead of
self.newMessageRefHandle = messageQuery.observe(.childAdded, with: { (snapshot) in
try replacing with
let childref = FIRDatabase.database().reference().child("ChildName")
childref.queryOrdered(byChild:"subChildName").observe(.value, with: { snapshot in
I have a situation in which my Firebase reference is not invoking the callback for observeSingleEvent.
let uid = FIRAuth.auth()?.currentUser?.uid
FIRDatabase.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { (snapShot) in
//code here not called
}) { (error) in
//code here not called either
}
In my case it appears the uid property which is coming from FIRAuth.auth()?.currentUser?.uid is somehow being cached from a previous app installation when I'm switching between Firebase environments (building with different dev and staging .plist files.)
However, if I just add a random non existing string in place of uid the callback occurs as expected with an empty snapshot for a missing reference node.
I believe it's relevant to mention that I'm using synced Firebase references with persistanceEnabled = true.
I'm in a situation where I'm switching between environments and my app is hanging because promises are not being fulfilled or rejected because the results of by observeSingleEvent method call are never completing.
I defined a class in a project, to manage my database set up in Firebase. The following is what I've done with the class so far.
import Foundation
import Firebase
class db{
class func getPrim() -> [String]{
var ret = [String]()
let ref = FIRDatabase.database().reference()
ref.child("bunya1").observeEventType(FIRDataEventType.Value, withBlock: {
s in
ret = s.value! as! [String]
})
print("ret: \(ret)")
return ret
}
}
And the method is called in a print() method, like print(db.getPrim()). But what the console(or terminal? anyway the screen on the bottom of xcode..) says is only an empty array. I embraced the statement above with print("-----------------------").
-----------------------
ret: []
[]
-----------------------
2016-09-07 20:23:08.808 이모저모[36962:] <FIRAnalytics/INFO> Successfully created Firebase Analytics App Delegate Proxy automatically. To disable the proxy, set the flag FirebaseAppDelegateProxyEnabled to NO in the Info.plist
2016-09-07 20:23:08.815 이모저모[36962:] <FIRAnalytics/INFO> Firebase Analytics enabled
Seems like ret in .observeEventType() method does not take its value out of the method block. As far as I know the data is supposed to be kept.. Can anyone give me a hint? I still don't understand how the code block as a method parameter works. Thnx!!
All firebase operations are by definition asynchronous which means your program doesn't wait for the data from firebase before going to the next statement in your code. So by the the time your print statements are called the data from firebase hasnt been fetched yet.
Take a look at this answer for more information.
André (and the link to Vikrum's answer) are indeed why this is happening. But it's usually easiest to understand if you add a few log statements to your code:
class func getPrim() -> [String]{
let ref = FIRDatabase.database().reference()
print("Before observer");
ref.child("bunya1").observeEventType(FIRDataEventType.Value, withBlock: {
s in
print("In observer block");
})
print("After observer");
return "..."
}
When you run this code, the logging will be in this order:
Before observer
After observer
In observer callback
This is probably not the order in which you expected them to appear. But it definitely explains why your returning an empty array in your snippet. If the code inside the block hasn't run yet, the item hasn't been added to the array yet.
The reason this order is inverted is as André and Vikrum say: the call to Firebase happens asynchronously. Since it can take some time (especially if this is the first time you're accessing the database), the Swift code continues executing to ensure the app stays responsive. Once the data comes back from Firebase, your block is called (for that reason it's sometimes referred to as a "callback") and you get the data.
I have a simple iOS app that syncs 1,000 Contacts with my Firebase. persistenceEnabled is set to true in my AppDelegate.
In my UITableViewController I observe the ChildAdded event, download the Contacts and this works fine.
The problem is this - the next time I open the app, navigate back to the UITableViewController, the ChildAdded event fires again and loads the 1,000 Contacts.
What I thought would happen is that on subsequent launches the ChildAdded event would not fire since persistenceEnabled is true and no new Children have been added.
Have I misunderstood how Firebase works ?
ref.child("contacts").observeEventType(.ChildAdded, withBlock: { snapshot in
if let json = snapshot.value as? NSDictionary {
for key in json.keyEnumerator() {
if let dict = json.valueForKey(key as! String) as? NSDictionary {
let contact = Contact(data: dict)
contacts.append(contact)
}
}
}
})
From the Firebase documentation on .ChildAdded:
The FIRDataEventTypeChildAdded event is triggered once for each existing child and then again every time a new child is added to the specified path.
Using persistence doesn't make a difference to this behavior. It just ensure that the event will also work in this way if the app is started while you don't have network connectivity.
Since You are registering observeEventType not observeSingleEventType. So register event only once in viewDidLoad. For the first time you will get all the data available in that node. next time onwards it will give you only the new child added. In case if you want to query firebase every time then register observeSingleEventType but again it will return all the data available in that node and will remove observer as it is observeSingleEventType.
use this method :
Database.database().reference().child("anything").observeSingleEvent(of: .value, with: { snapshot in ...
this will fetch data once ..