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

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.

Related

iOS 12 specific problem: Core Data External Storage Binary Data corruption

I've spent the better part of a workday trying to solve this.
Background
I have a simple core data model, with books and reading sessions. The books have covers (images) that are stored as binary data with "Allows External Storage".
On iOS 11.4 and below, everything works fine all the time. When I save a new session everything gets updated properly.
Problem
Since iOS 12, when I create a new reading session and link it to the book, about every second time, core data generates a SQL statement that also updates the book cover field, sometimes resulting in a bad reference (to file on disk) which often results in the cover being nil when restarting the app, and almost always creates duplicate copy of the cover on disk (as can be seen in Simulator's _EXTERNAL_DATA folder).
In-memory context and objects remain correct though (and everything in the UI is therefore OK), until the app is restarted, then the cover is often nil.
iOS 12 specific
On iOS 12, I can deterministically reproduce the error in the simulator, on physical devices, and users have reported the error as well. I cannot reproduce the error on iOS 11.4, and no users reported the error previous to iOS 12.
Steps taken
I've enabled "-com.apple.CoreData.ConcurrencyDebug 1", so it shouldn't be that I'm accessing anything from the wrong queue. I've also enabled "-com.apple.CoreData.SQLDebug 3" so that I can see exactly what gets written.
I've made sure the Book instance (and therefore the cover) is not modified by my code before the association with the new Session by checking hasChanges, just before I do newSession.book = book and context.save().
To be 100% sure I'm not touching the cover property on any thread I've short-circuited my getters and setters for that property. No improvement.
I've tried using objectID to request an instance of the book just before the association and save. No improvement.
I've even tried the option where the context keeps strong references to all objects, just to make sure it was not some kind of memory management issue. No improvement.
Question
Any ideas for next steps?
Status update
This is a defect in iOS 12. See accepted answer below for a detailed description of a resonable workaround.
Update: The underlying Core Data issue appears to be resolved in iOS 12.1 (verified in beta 4). We will keep the workaround described below in our app, and won't be recommending using the External Storage option any time soon.
After talking to Apple engineers and filing the Radar mentioned above, we couldn’t wait around for a fix, so we took the hit and switched to storing files on the filesystem and managing it directly ourselves.
Another alternative that we considered was migrating our model not to allow External Storage for BLOBs, but I don't know what impact that would have had on performance and I was also worried about a model migration at a time when this part of iOS seems to be unstable, especially after reading stories like this in the past: Core Data: don’t store large files as binary data – Alexander Edge – Medium
It wasn't too much of a pain to implement local storage ourselves. You just need to have a unique identifier for each record that you can use to create a filename so you can map files to records. We added an extension to our Managed Object subclass with methods for reading, writing and deleting the files. Now, instead of calling e.g. article.photo = image.pngData(), we now need to call something like article.savePhoto(image.pngData()) and then we do similar when we want to retrieve the image. You can also add some code to these methods to support backwards compatibility with any images that are currently stored in Core Data.
Deletion was a little more tricky because our objects are deleted from multiple places in the code, including cascading deletes. In the end I opted to do it in the managed object's prepareForDeletion method but it is not ideal. There is plenty of discussion of how best to implement this here: cocoa - How to handle cleanup of external data when deleting unsaved Core Data objects? - Stack Overflow
Finally, to prevent our app crashing when a non-Optional binary attribute has disappeared because of this bug, I override awakeFromFetch in my Managed Object subclass to ensure that any required attributes are not nil, and if they are, I set them to a placeholder image so that they can be saved without the validation failing.

Firebase .observeSingleEvent(of:with:) method is retrieving cached/old data [duplicate]

This question already has answers here:
Firebase Offline Capabilities and addListenerForSingleValueEvent
(4 answers)
Closed 4 years ago.
[Disclaimer] I have personally posted and answered this question after having struggled with it myself and, relevantly, noticed that many people still do
Context
I am developing an iOS mobile application and - for this particular project - decided to use the Firebase Realtime Database as my backend infrastructure.
Problem
When querying data at a specific node using the .observeSingleEvent(of:with:) method, I always find myself retrieving either cached or old data rather than the newly updated one.
In some cases, calling the method twice in a row retrieves the desired server data.
Attempts
Used .keepSynced(true) at the relevant node which, according to the Firebase documentation
automatically downloads the data at these locations and keeps it in sync even if the reference has no active listeners
Overview
Going through the documentation, you notice that there are two primary ways of querying data from the Firebase Realtime Database into your iOS mobile application
The .observe(_:with:) method which, according to the Firebase Documentation, continuously listens for changes at a particular node and triggers the callback every time the data changes at the latter.
This method is triggered once when the listener is attached and again every time the data, including any children, changes. The event callback is passed a snapshot containing all data at that location, including child data. If there is no data, the snapshot will return false when you call exists() and nil when you read its value property.
The .observeSingleEvent(of:with:) method which, according to the Firebase Documentation, is called exactly once.
In some cases you may want a callback to be called once and then immediately removed, such as when initializing a UI element that you don't expect to change. You can use the observeSingleEventOfType method to simplify this scenario, [in which] the event callback [is triggered] once and then does not trigger again.
Problem
After going through the different possible methods of querying your data, you've realized that the .observeSingleEvent(of:with:) method suits better your current database-reading needs. However, implementing it in your application keeps on retrieving cached and old data no matter how many times you modify your database. You've called the .keepSynced(true) at the relevant database reference, yet in vain. You've opted for the .observe(_:with:) method instead, and everything starts to work perfectly fine.
So what might be the issue?
Solution
The reason you might be going through this problem is perfectly logical if you have invalid database security rules. These can easily prevent you from retrieving your desired data and synchronizing your realtime database.
Let's assume you are trying to synchronize the myRef database reference. You need to set the correct rules that allow reading from this database reference - something along the lines of ".read" = true".
[Warning] Please be careful with these database security rules. Incorrect rules can lead to drastically undesired behaviors, such as people illegally reading and/or writing from/into your database. A good video on how to set flawless security rules is The key to firebase security - Google I/O 2016

Firebase observing adding new records

Before you link me to a duplicate, please read what I'm asking..
I'm building an app which basically has a list of about 5000 teams. These teams are fairly static (they don't change very often). I would like to observe any time one is changed though as it's essential it get's updated in the app ASAP.
If I include dbTeams.ref.observe(.childAdded, with: {}), it runs each time the app starts, loading over all 5000 records despite having them in the persistent storage already (I have enabled persistence).
Now the documentation says this will happen, I know, but with 5000 records (and potentially way more in the future), I can't have this happen.
My options so far (from what I've found and tried) are:
Add a timestamp to each record and create a custom query to call .childAdded after the last timestamp... This is inefficient. Storing a timestamp for soccer teams which will hardly ever change, is silly. It also means keeping a copy of the last time it was checked.
Create a sub-list within the Teams list. This too is silly as you may as well call .value and get the whole bunch of data in one go.
Just live with it... Fine - until it scales to tens of thousands of records. Not clever either.
It just seems weird that all the other event listeners only fire when they are "supposed to" except this one.
Any help would be appreciated - how do I achieve what I need?

Is there a way to run code during an update for an iOS app?

I have recently discovered an error in an app I have previously released for iOS. I am saving something with an incorrect metadata variable which causes a notification to be monthly instead of daily.
This is trivial to fix, it just involves looping through all the currently saved objects and changing a 1 to a 0. However it would be preferable to me to be able to do this during the update that fixes the error. Is there a way then of running code as part of an update?
Otherwise I imagine the next best way of solving the problem would be to run the loop the next time the app is opened.
I have looked around online and have only found solutions which involve running one off code on app start up. I am therefore wondering if I am missing something because this seems to be the sort of thing that a lot of apps will need to have happen to them if they need to reconfigure how their data is saved or equivalent.
If you are not using core data and overriding the light weight migration to make an changes needed there, then the only way is to use User Defaults and check for a var you know does not exist, run the code, then add the var so as to not run it again.
func updateDB() {
if !UserDefaults.standard.bool(forKey: MyProject.DBFixKey) {
// Fix DB
UserDefaults.standard.set(true, forKey: MyProject.DBFixKey)
}
}

NSAsychronousFetchResult after the app is DidEnterBackground

I have a client app with coredata as its back end. Its simple enough, with two entities.
UPDATE: Using CloudKit as the sync service. I'm not really sure what is going on there. Except I can query and get results, incase things don't automatically work. The problem is, as I noticed with most third-party sync-service providers. 95% of the time, they all work. Its when I test it with a more than a few devices / simultaneous calls that some undesired change comes in.
This question is more about iOS and coredata than the actual syncing architecture.
there are times when there is definite sync data loss. I really can't tell when and how. That im still figuring out. Sometimes initial sync takes a long time (if theres existing data) and the user might close the app, (some people double press the home button and actually close apps!).
But no matter what I do, sometimes i miss an object, sometimes an attribute.
So I saw this NSAsynchronousFetchRequest and I thought about giving me an option to check if all local-data (coredata) is okay, to see if theres anything missing.
Perhaps i could use a simple predicate to see of some managedObject.title == nil and fetch its identifier. Collect those faulty objects and request the truth server for data for these objects? Is this a good use of NSAsynchronousFetchRequest?
If yes, when during the lifetime of the app would this be good?
Im thinking maybe after applicationDidEnterBackround would be a good time..? Then If I do get it, will need a good way to manage CoreData in the background!
If no, well.. Really don't know wat to do then.
Im trying to actually do this, will update with my results.
UPDATE: Question updated to reflect the use of Cloudkit

Resources