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.
Related
I am in process in adding CloudKit to my app to enable iCloud sync. But I ran into problem with my method, that executes query with perform method on private database.
My method worked fine, I then changed a few related methods (just with check if iCloud is available) and suddenly my perform method does nothing. By nothing I mean that nothing in perform(query: ) closure gets executed. I have breakpoint on the first line and others on the next lines but never manage to hit them.
private static func getAppDetailsFromCloud(completion: #escaping (_ appDetails: [CloudAppDetails]?) -> Void) {
var cloudAppDetails = [CloudAppDetails]()
let privateDatabase = CKContainer.default().privateCloudDatabase
let query = CKQuery(recordType: APPID_Type, predicate: NSPredicate(format: "TRUEPREDICATE"))
privateDatabase.perform(query, inZoneWith: nil) { (records, error) in
if let error = error {
print(error)
completion(nil)
} else {
if let records = records {
for record in records {
let appId = record.object(forKey: APPID_ID_Property) as? Int
let isDeleted = record.object(forKey: APPID_ISDELETED_Property) as? Int
if let appId = appId, let isDeleted = isDeleted {
cloudAppDetails.append(CloudAppDetails(id: appId, isDeleted: isDeleted == 1))
}
}
completion(cloudAppDetails)
return
}
}
completion(nil)
}
}
My problem starts at privateDatabase.perform line, after that no breakpoints are hit and my execution moves to function which called this one getAppDetailsFromCloud. There is no error...
This is my first time implementing CloudKit and I have no idea why nothing happens in the closure above.
Thanks for help.
EDIT: Forgot to mention that this metod used to work fine and I was able to get records from iCloud. I have not made any edits to it and now it does not work as described :/
EDIT 2: When I run the app without debugger attached then everything works flawlessly. I can sync all data between devices as expected. When I try to debug the code, then I once again get no records from iCloud.
In the completion handler shown here, if there's no error and no results are found, execution will fall through and quietly exit. So, there are two possible conditions happening here: the query isn't running or the query isn't finding any results. I'd perform the following investigative steps, in order:
Check your .entitlements file for the key com.apple.dev.icloud-container-environment. If this key isn't present, then builds from xcode will utilize the development environment. If this key is set, then builds from xcode will access the environment pointed to by this key. (Users that installed this app from Testflight or the app store will always use the production environment).
Open the cloudkit dashboard in the web browser and validate that the records you expect are indeed present in the environment indicated by step 1 and the container you expect. If the records aren't there, then you've found your problem.
If the records appear as expected in the dashboard, then place the breakpoint on the .perform line. If the query is not being called when you expected, then you need to look earlier in the call stack... who was expected to call this function?
If the .perform is being called as expected, then add an else to the if let record statement. Put a breakpoint in the else block. If that fires, then the query ran but found no records.
If, after the above steps, you find that the completion handler absolutely isn't executed, this suggests a malformed query. Try running the query by hand using the cloudkit dashboard and observing the results.
The closure executes asynchronously and usually you need to wait few seconds.
Take into account you can't debug many threads in same way as single. Bcs debugger will not hit breakpoint in closure while you staying in your main thread.
2019, I encountered this issue while working on my CloudKit tasks. Thunk's selected answer didn't help me, so I guess I'm gonna share here my magic. I got the idea of removing the breakpoints and print the results instead. And it worked. But I still need to use breakpoints inside the closure. Well, what I had to do is restart the Xcode. You know the drill in iOS development, if something's not right, restart the Xcode, reconnect the device, and whatnot.
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)
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 ..
I was trying to read the old value from firebase, update it, then save it back with the following code:
var unreadChatsRef = usersRef.childByAppendingPath("\(user.objectId!)/unreadChats/\(self.chat.objectId!)")
unreadChatsRef.observeSingleEventOfType(FEventType.Value, withBlock: { (snapshot) -> Void in
if snapshot.exists() {
var msgCnt = snapshot.value as? Int
msgCnt = (msgCnt ?? 0) + 1
unreadChatsRef.setValue(msgCnt)
}
})
Previously I made a mistake and used observeEventType instead of single event so by saving the value, it triggers itself to update the value again and again. I stopped my iOS program while seeing this in debug session. Somehow none of the subsequent updates made it to the firebase server. I had the local cache enabled by calling Firebase.defaultConfig().persistenceEnabled = true. So I end up with a cached value different from the value on the server.
What puzzles me is that even if my other iOS chat client updates that number, this iOS device (the code above) continues to see the cached value and can never fetch the correct value from the server. Am I doing something wrong? How do I tell my program to invalidate the cache and fetch a fresh copy from the server?