Realm Sync data resets on app reinstall - ios

I have an iOS app which uses Realm locally and it works great, and my intention is to use Realm Object Server to enable:
Data sync across devices
Data restoration on app reinstall
I've been having a lot of trouble with the last one. The flow in my app is the following:
First, it tries to find a User in my Realm Sync. If a User exists, go to step 4. Otherwise, go to step 2;
Ask for the user's name, income and payday;
Get the user's userRecordID for iCloud and logIn to Realm Object Server.
Use the SyncUser's identity as the User entity identifier. Save the data I just asked (name, income and payday) to Realm;
Go to main app screen and let the user enjoy the app.
ROS is using cloudkit based authentication. The problem occurs if I follow steps 1-4 above, and then reinstall the app. I expected the previously created User to be retrieved in step 1, but instead all of its fields beside the primary key are set to blank values (name becomes empty string, income and payday become 0). Is this behavior expected? Is what I want to do within the purpose of Realm?
EDIT
Made the flow clearer after #AustinZ's answer.

"Data restoration on app install" is definitely a valid use case for Realm Sync. However, the flow you describe is problematic.
A synchronized Realm is identified by two pieces of information: a sync user and the Realm's path on the Realm Object Server (e.g. /~/my/realm). If you have the same sync user logged into multiple devices, and they each open a copy of a Realm at the same path, they are opening the same synced Realm and will stay in sync with each other.
However, to get the same sync user, a user needs to log in to the Realm Object Server from their device. You do this by creating a CloudKit SyncCredentials value using SyncCredentials.cloudKit(token:) and passing that credential to SyncUser.logIn(). The device will then communicate with the Realm Object Server, and if the log in succeeds, then the logIn() method's callback block will be invoked and will give you a SyncUser. That is the sync user you need to use to open Realms.
So:
You shouldn't be saving users or user info to Realms manually. We persist the relevant information automatically.
If a user deletes and re-installs their app, the app's documents and files are deleted as well, which means you shouldn't assume you can access a Realm's data after the app which created it was re-installed.
What you should be doing is, in step 3, using the user's CloudKit credentials and logging into the Realm Object Server as described above.
Please review our documentation for more information.

Related

When using NSPersistentCloudKitContainer: what to do if the user logs out of iCloud?

From several Apple WWDC talks on CloudKit, when using CloudKit to sync private user data across devices, when the user logs out of iCloud, the App is supposed to empty the on-device cache / local replica (I use Core Data for on-device permanence). Then when this user (or any user really) logs back into his respective iCloud account on that same device, the device is then supposed to sync back down the data from the corresponding iCloud account. Makes sense!
I was wondering how I can achieve this when I use NSPersistentCloudKitContainer (instead of writing all that CloudKit code myself). I looked at the sample code related to Apple WWDC 2019 session 202 ("Using Core Data with CloudKit"), and this code does not what I want (the sample just does not focus on the iCloud account part, that is why they do not bother to empty the cache, I think): Indeed the NSPersistentCloudKitContainer stops synching if I log out the user, but it picks up again synching when I log back in. That is actually more like the desired (and indeed also actual, which is good) behaviour by the sample App when my web connection goes offline for a while.
But if the user logs out of iCloud (I use CloudKit framework to be informed about user account logout and login status), what I would need to do is somehow:
"disconnect" the sqlite store from the NSPersistentCloudKitContainer, then
empty (or even delete) the database, and then,
when the old (or another) user logs in, initialise a new NSPersistentCloudKitContainer (that will recreate an empty database as if the App would be used for the first time ever), so communication with the respective Cloud database can proceed.
I can not just empty the database WHILE being "connected" to the NSPersistentCloudKitContainer, as it records all the deletes and as soon as the (same) user logs in again, these deletes are synched to the Cloud, which I of course do NOT want.
Is this how one does it, aka essentially follow these three steps?
And if yes, how do I do step 2? Since I must use some kind of NSPersistentContainer to talk to the database, Apple warns not to directly do file system operations but only via Core Data. So do I need to init a "standard" NSPersistentContainer (without a communication pipe to the Cloud), and then destroy the database (e.g. using e.g. the coordinator instance method destroyPersistentStore())?
Or is there another approach for achieving the same thing?
Thank you very much for any help!!

Realm Object Server - When does syncing occur?

I have an iOS app powered by a Realm Object Server hosted on Amazon and I am curious as to when syncing actually occurs. Is syncing lazy on queries, real-time for all open realms, or something else?
For example, let's say I have a realm called /common and all SyncUsers have read/write permissions to this realm. Each active SyncUser also has a notification block listening to a GlobalProfile object in the /common realm corresponding to their SyncUser identity. If one user makes changes to another user's GlobalProfile object, will all users download this change immediately, or will only the user with the notification block on this object immediately download the change?
Specifically, I want to create a way for a user (let's name it Tom) to search for other users and send them read/write permissions to Tom's realm. My current solution is a public realm in which each user adds a GlobalProfile object. To search for a user named Jerry, Tom can simply query the public realm. To grant Jerry read/write permissions to Tom's realm, Tom can write a SyncPermissionOffer.token into Jerry's GlobalProfile. Because Jerry has a notification block listening to his GlobalProfile, he will be immediately notified of this token and can accept the SyncPermissionOffer to Tom's realm. My worry, however, is that all users, not just Jerry, will sync this change in the /common realm, which is unnecessary. If the app has 100,000 users, I don't want each user to constantly sync all SyncPermissionOffer tokens being sent between other users.
Please let me know if this question is not clear. Thank you so much for the help!
Sync is real-time on all changes. Sync is not waiting for, or contingent on, a notification block. The notification block is executed locally and is not used to alter the client-server communication in any way.
If you have a /common Realm, it will contain all your permission offers and
all users will download all other users permission offers. It will grow large over time.
What you could do is to have a /common Realm that only contains the user ids, their "names", and a path to their "inbox" Realm. When user A wants to
invite user B, A looks up B in /common, obtains the Realm path for B's inbox, and sends a permission offer to that.
With this approach, the common Realm would not grow that big, because the data par user could be kept below 100 bytes, say.

Transparent login with the Realm Mobile Platform

I'm moving an app from using CloudKit sync to using the Realm Mobile Platform.
I want the login process to be transparent to the user, so I'm using CloudKit authentication, this way they don't have to worry about creating an account or remembering a password. Of course if the user doesn't have an iCloud account set up, the data will not sync, but that's how the app is already working without realm: we just alert the user to the fact that the data will not sync because an iCloud account could not be found.
I noticed in the documentation that in order to open the synchronized realm, I have to provide the user's credentials.
My question is: how do I handle the case where the first time the user launches the app he doesn't have internet connection or doesn't have an iCloud account setup? I would like to just store the data locally if the user doesn't have an iCloud account (this is how the app works currently), but if I understand it correctly, the only way I can open the synchronized realm is to have the user's credentials, which I need internet access and an iCloud account to be set up on the device to get. Is there an easy way to handle that case with realm?
I know I could have a separate offline storage and move its data to the synced realm once on-line, but that would be tricky to get right and quite a bit of work.
Unfortunately you are correct: the very first time the user logs in to the Realm Object Server (via +[SyncUser logInWithCredentials:...]), the user needs to already have whatever identity provider account they're going to use (in your case, an iCloud account), as well as Internet connectivity.
Once the user's logged in at least once, their Realm credentials are persisted internally and the user can be used subsequently to open up Realms immediately, even if there is no connectivity.
The best workaround right now is to use a non-synced Realm to store whatever data you need before you have an opportunity to log in, and then manually copy the data over to your synchronized Realm once you are able to log in the user successfully and open the synced Realm with the user.
We know this is a pain point for a lot of our users, and plan to address this deficiency in the near future with features to let you immediately start using 'synced' Realms before a user has logged in.

Share database [core data] between different devices, not just the user's ones

What is the best way, to share a database between different devices, that are not just the user’s ones, but for example could be his friend’s phone. That means that iCloud is not an option.
Example:
 All of my data is app-user specific, so basically:
user logs into my app, do some work
then he can log in with the same acc on his friend phone and data should be the same
Is there an any way to upload the whole user specific database to some online storage provider (like firebase,… ) and then download it on another device and initialise core data stack, when the same user logs in on a different device?
Or is it the only way to sync data with the server and than preload the database?
You could simply upload the whole database file(s) and then download it on another device. The problem though is portability. You need to ensure that both devices support the same version of the database so they are compatible. To port the same thing to another platform is again a different story but doable when not using core data.
Then there is a problem of conflicts. Imagine you forget to log out from the second device and you open it after a week and the database is accidentally synced back to the server. This will make you lose all the data you created on your "main" device.
So in general it is possible to sync the whole thing but you will have loads of issues. You should create a server that supports all entities and works through ids (so you know the object was modified and not created) and date modified to be able to resolve conflicts.
Syncing data between multiple devices is the biggest reason to use something like Firebase. That's one of its primary purposes. You would use Firebase for data storage instead of Core Data, and it would automatically handle syncing between devices. You don't write code to upload or download anything, you just read and write Firebase data and it handles the syncing. It supports user accounts, so if a user logs on on a different device, their data automatically syncs to that device. There are numerous other options besides Firebase, of course.
CloudKit also syncs between different devices, but it's linked to the current iCloud account on the phone. Since you want in-app login, it's not so good.

Using Firebase with multiple users in an iOS app

A number of iOS apps I develop are used in scenarios where the Organisation has a number of Drivers and the iPhone is shared between them. Each Driver has his own app credentials, logs in to the app, and is presented with his own set of data. The Firebase data is persisted as he is offline most of the day and I use keepSynced(true) to ensure data is up-to-date.
The problem I'm having is that once a user logs out with the Firebase method FIRAuth.auth()!.signOut() then when a different user logs in, he sees the previous users' data. I'm guessing that because the data is persisted it is not cleared from the cache. However, I am retrieving the data by the user id so this doesn't quite explain it.
What is the best pattern to permit multiple users to use the same iOS app with Firebase ?
Try creating multiple instances of FIRApp, each with a different name, using the +configureWithName:options: method. Then use the different apps in FIRAuth +authWithApp and elsewhere as needed.
I haven't actually done this and am too new to Firebase to say it's the best pattern, but it seems like it should work.
One thing to note from the docs is that, "On Android and iOS, Analytics are only logged for the default app." One solution might to keep the __FIRAPP_DEFAULT app, and then make a named app for each user type.

Resources