Retrieve data/snapshot after manually remove children in Firebase - ios

I have a function that uses .observeSingleEventOfType with .Value to retrieve data from a subtree from Firebase. The issue I'm facing right now is that, every single time I manually remove children/data in Firebase to test how this function behaves, it always loads the old data the first time when it gets called after deletion. After the first time, it then loads the correct data.
I have tried to use .ChildAdded and .observeEventType, but the behavior didn't change. I have Firebase.defaultConfig().persistenceEnabled set to true now, and I am guessing the problem is that snapshot reads data from cache if it's available, otherwise checks Firebase database.
Any one experiences this kind of issues before?
Any help would be highly appreciated.
UPDATE:
I have tested with and without persistence enabled, it turns out that I was correct, when Firebase.defaultConfig().persistenceEnabled caches all data in the past to memory, and the observe functions will try to load from memory first then go to Firebase.
When I set Firebase.defaultConfig().persistenceEnabled = false The problem went away. However, I would like to have my app work offline as well which mean I do need to set Firebase.defaultConfig().persistenceEnabled = true Is there a way to have all observe functions cache data except this specific one?
SOLUTION:
The problem is fixed now, the issue was that I didn't remove observers when the view controller was dismissed. So every time I enter the view again, another observer gets called and thus I always receive more than one call backs.

Related

What problem viewContext.setQueryGenerationFrom is trying to solve in CoreData project which involves transaction history?

I have came across 2 demo CoreData projects, which involves transaction history.
Both are using
viewContext.setQueryGenerationFrom(.current)
when they initialize their CoreData stack.
FireballWatch demo from raywenderlich
The demo is picked from https://www.raywenderlich.com/14958063-modern-efficient-core-data
The author is trying to demonstrate, how to make use of transaction history, to update UI correctly after batch insertion.
However, it isn't clear on what problem viewContext.setQueryGenerationFrom(.current) is trying to solve.
Code : https://github.com/yccheok/FireballWatch_Materials/blob/main/final/FireballWatch/Model/Persistence.swift#L100
Brief explanation of the article https://www.raywenderlich.com/14958063-modern-efficient-core-data doesn't tell much about the idea behind setQueryGenerationFrom.
You are pinning the view context to the most recent transaction in the
persistent store with the call to setQueryGenerationFrom(_:). However,
because setting query generation is only compatible with an SQLite
store, you do so only if inMemory is false.
Synchronizing a Local Store to the Cloud from Apple
The demo is picked from https://developer.apple.com/documentation/coredata/synchronizing_a_local_store_to_the_cloud
It is trying to demonstrate, how to use transaction history, to prevent data duplication after syncing with CloudKit.
However, it is still not clear on what problem viewContext.setQueryGenerationFrom(.current) is trying to solve.
Code: https://github.com/yccheok/SynchronizingALocalStoreToTheCloud/blob/main/CoreDataCloudKitDemo/DataProvider/CoreDataStack.swift#L89
Not much explanation is given behind the idea on setQueryGenerationFrom.
Experiment
No matter whether I have included viewContext.setQueryGenerationFrom(.current), or excluded viewContext.setQueryGenerationFrom(.current) in my CoreData stack, I am having the same observation in both situations.
Able to observe UI update immediately, after I save a new NSManagedObject, with context.save called.
Able to observe UI update immediately, after I edit an existing NSManagedObject, with context.save called.
Able to observe UI update immediately, after I perform batch NSBatchUpdateRequest operation, with mergeChanges called.
Able to observe UI update immediately, after I perform batch NSBatchDeleteRequest operation, with mergeChanges called.
There are some good graphical explanation on what is doing by setQueryGenerationFrom
https://cocoacasts.com/what-are-core-data-query-generations
However, I fail to relate it to, what kind of real problem setQueryGenerationFrom is trying to solve.
Does anyone know, what problem viewContext.setQueryGenerationFrom is trying to solve in CoreData project which involves transaction history? Would be appreciate, if there is a solid demo code example, to show what kind of problem is solved by setQueryGenerationFrom. Thank you.
It just pins some snapshot of context, so all your following queries work with exactly that snapshot, independently of what's happened after pin-moment. It's like detached checkout from GitHub - everyone goes ahead but you work with out sandbox.
This is guaranty of consistency which could be needed for some sequence of requests between which no changes should happen.
To pin we use viewContext.setQueryGenerationFrom(.current)
To unpin and continue with kind-of-HEAD we use viewContext.setQueryGenerationFrom(nil)
Additional description is in Apple's article
Taking the demo project CoreDataCloudKitDemo, it is to support the case where device1 is editing a post, and device2 deletes it. You can take look for this piece of code:
// The selected post was changed, and the user isn’t editing it.
// Show an alert, and go back to the main view to reload everything after the user taps reload.
let alert = UIAlertController(title: "Core Data CloudKit Alert",
message: "This post has been deleted by a peer!",
preferredStyle: .alert)
With the magic of query generation, we will be able to immediately pop up this alert on the device1 which is editing the post, after device2 device deletes it.
On the main listing view, there are no issues, as the item will probably just be animated out of the list. The issue is for the details view, what is going to be shown, and what happens if the user edits the value the details view.
In samples codes, this is usually following
container.viewContext.automaticallyMergesChangesFromParent = true
As in the details view, usually we will be holding on to an object, and once it is deleted, the object itself should become invalid, and accessing it or the attributes will cause unexpected behaviours.

Some questions about keepSynced(true) on a limited query reference

I have an Firebase-backed app with the following database structure:
posts
/uid
/postId
Originally, i'd load data from the posts/uid node using ObserveEventOfType with .childAdded. This would load stale data frequently (~5 times a day) for all users of my app simutaneously. When attempting to update the data by making a new post, Firebase would still return stale data.
As a result, I decided to try keepSynced. Now, if my reference looked like this:
reference = Database().database.reference.child("posts").child(uid)
keepSynced would load all of the data at that node, which could amount to very large downloads if there are many children in that node. So, I decided to change the reference/query to:
reference = Database().database.reference.child("posts").child(uid).queryLimited(toLast: 25)
When turning keepSynced on for this node, it syncs for the last 25 children in the node successfully. However, I still am facing the issue of receiving stale data rather frequently. So here are my questions:
When adding the keepSynced mode on the limited query, does it only sync from the initial node you added it to, or does it always just sync the 25 latest children under that node?
Where is the best place to add the keepSynced(true) line in code? Before we load the reference, in viewWillAppear, or inside of the actual download callback?
Similarly, where is the best place to use keepSynced(false)?
Do the keepSynced listeners delete when the app fades into the background?
Why does keepSynced sometimes not address for child updates?
I currently use keepSynced(true) inside of the function I use to load posts which is called on viewDidLoad.
Thanks in advance.
As its name implies keepSynced(true) keeps whatever query or reference you call it on synchronized in the local cache. It quite literally just attaches an empty observer to that query/reference. So in your Database().database.reference.child("posts").child(uid).queryLimited(toLast: 25) it will sync the last 25 child nodes, and keep synchronizing those (removing previous ones as new ones are added).
Firebase Realtime Database caching mechanism works most reliably if you repeatedly listen for the exact same data. Specifically, a .value listener attached to Database().database.reference.child("posts").child(uid) may not see data that was cached through Database().database.reference.child("posts").child(uid).queryLimited(toLast: 25). This is because the Firebase client guarantees to never fire events for partial updates, and in this example it can't guarantee that it has all data from the first reference.
For your questions:
See above...
It's most common to add them in viewWillAppear.
I'm not sure why you'd want to call keepSynced false, so can't recommend anything there.
Not sure if this is what you mean, but keepSynced(true) is not persisted between runs of the app. So you have to call keepSynced(true) each time your app/view starts.
See above...
In general you seem to try and work around the way the API works, by calling the API in different ways. I typically don't see great results from that. If you want your app to behave differently than the API does, consider creating a custom wrapper, and caching the data there.

Firebase iOS - Snapshot isn't updated but database is?

I've been having this issue at random times for quite a while, where I will physically be looking at my firebase console and see that I have deleted a piece of data, and then in code I will call print(snapshot.ref) and see the correct reference (copy and pasted in browser to double check too), yet somehow when I try to get the values of the snapshot/iterate over its children the snapshot is containing old data that is not in the database anymore.
let key2 = ref.child(users).child("Employees").observeSingleEvent(of: FIRDataEventType.value, with: { (snapshot) in
print(snapshot)
for child in snapshot.children
{
self.nameList.append((child as AnyObject).value)
}
})
So here my database looks like this: (picture is cut off but there's no children under it)
Yet somehow when I print snapshot I get:
Snap (Employees) {
0 = "";
1 = "name1";
2 = "name1";
}
This has been frustrating me for a while, it seems like it could have something to do with old snapshot values somehow being stored locally or somehow not seeing the most up to date version of the database. If it matters I have similar calls to .observeSingleEvent in this file, the one copy and pasted above is nested within another. Even if it were a synchronization problem, I still don't know how that could make the printed value the old value.
Any help would be so so appreciated.
This behavior is apparently by design. It's so strange that I actually contacted Firebase Support about it, and was told that they'd consider revising either the behavior or the docs, but couldn't promise a date and I should monitor their Release Notes URL for updates to it.
It makes a little sense if you consider it from the SDK point of view. You're calling observeSingleEvent. To Firebase this means they should only call you ONE TIME. Developers would probably find it confusing if a method with that name produced more than one callback, right?
But if you have persistence enabled things get a little weird. Just like with observeEventOfType, Firebase will give you the on-disk value immediately so you get the fastest UI update, but then it will call the server for a fresher value to be sure it has the latest data from then on. The problem is, since you're telling it not to call you back with this data, it will remember it (so you WILL see it in the future, which is why it seems confusing) but not tell you that it's arrived.
What I've discovered through some trial and error is that the instinctive drive to use observeSingleEvent may be misguided with Firebase anyway. Both iOS and Android uses "recycler" view mechanisms for table/collection views such that only a handful of items are actually in-memory at a time anyway, even on screens with a lot of data. Beyond this built-in efficiency from the platform, Firebase itself seems to work just fine even managing many dozens of in-memory refs at a time. In my apps, I've taken to just using observeEventOfType for all of my use-cases except where I have a very specific, and not theoretical-efficiency-related reason, to use observeSingleEvent. The app performance has been minimal, and the data then works much more the way you expect.

Force Realm notification block to run before proceeding

The background for this question is that I have an app in which all the tableViews are updated automatically using fine grained Realm notifications. At some point I fetch data from a server to populate a table. In the completion block of the server call, I add the newly fetched object into Realm, triggering a notification that updates the table.
The problem I'm having is that I want to start an animation that uses the newly inserted table cell immediately after adding the object to Realm. Is there a way to force the notification block to run before I proceed to the next line? Here's little pseudo code to try to clarify
Network.getDataFromServer() { json in
let realmObject = Object(json)
realm.write({
realm.add(realmObject)
})
// Realm notification block needs to complete before getting here
runAnimation()
}
Has anybody encountered a similar problem? I really like using fine grained notifications to manage table views because it's so clean, so I'd really like to find a way to handle scenarios like this one.
The right way to do this in a fully reactive application would be to perform the animation based on the notification coming in. This way, no matter how an object is being added (getDataFromServer, a bg thread, or even a different process), your app would react the same way.
This leads to simpler code while being more robust.

Reset Firebase cache

While developing with Firebase, I manually added a data record in the console but forgot one entry, which caused the app to crash. I've corrected the problem in the console but, because I was using Firebase's data persistence, the original data error persists, causing the crash again. If I switch the persistence off, everything is fine but the cached store isn't being updated. Has anyone had this problem and found a way to solve it?
I too have faced this issue, and have lost sleep over the thought of users being trapped in an endless crash-on-start-up loop.
As suggested, the opportunity for the issue to arise is created in the time window between the start up of an app and the subsequent arrival of cache updates from the Firebase server. If data is read from the cache during this time window, and then, if the data happens to be missing an expected value, and then, if the app uses the data in a way that assumes the data will not be nil, the app crashes. If the app crashes before the cache updates, the cache will never have the chance to update, and the user will be trapped in an endless loop (absent wiping the app's data from the device's memory).
Thus far, I've dealt with the issue by more strenuously guarding against the possibility of nil values in code that is called during start up. If nil is checked and inconveniently found, then, depending upon the situation, either (1) if possible and if it will not lead to further data corruption, the app substitutes an appropriate value in place of nil, or otherwise (2) the app goes into a wait mode for a couple of seconds, and then initiates a fresh read from the problem node, and then reattempts the start-up routine.
Perhaps the moral of the story is never assume that a value is non-nil or otherwise within an expected range. Either validate the value upon receipt or check the value at the time of intended use or both, and then handle errors accordingly.
The cache should update automatically. Since this happens in a background thread without, while it invokes your code on the main thread, I'd expect it to update the disk cache even if you application code crashes.
But if that doesn't happen, the fastest way to get back in a good state is to clear the app data for you application or to completely uninstall/reinstall.

Resources