We are trying to implement client reset functionality in a Xamarin app that uses Realm cloud sync. And there are a couple of problems that we are experiencing. First and foremost, here are the steps that we perform to generate a client reset. Here you can find the sample code which is a Xamarin app to this question:
- We have a realm app set with anonymous authentication and sync enabled
- There’s a class called
MyInformation
in the sample project. - I’ve declared a property called
Test
in the cloud schema and I am not referencing that property in the sample app - To generate a client reset I simply change the type of the property from
string
todouble
and then back.
Our real app, of course, is much more complicated and it asks for the user confirmation before doing the client reset and has a much more complex schema, but we’ve distilled the code to the minimum to be able to reproduce the issues that I will explain below.
Data duplication issue
After generating client reset a process starts in the cloud that copies the data from the previous schema to the new schema. And this is the phase where we face the first issue. After we receive the ClientResetException
and invoke the InitiateClientReset
and receive the result of true
, which denotes the fact of the client reset being successful. At this point, we restart the app’s main UI. During that, we ensure that all of the changes are synced by invoking the following bit of code
await Task.WhenAll(_realm.SyncSession.WaitForDownloadAsync(),
_realm.SyncSession.WaitForUploadAsync());
Then, we have a step where we check if there is already MyInformation
data in the database or not, and if there is not, we generate the data. This is the simplified version of what we do in the real app, where we have to bootstrap the data of the user if it hasn’t been done before. And the problem is that even though we’ve asked the app to wait for the pending changes to sync and we do have MyInformation
in the cloud-synced DB, _realm.All<MyInformation>()
would return no results and our data generation logic would kick in, essentially duplicating already existing data. I guess the problem is that the data is not yet available in the new realm instance as it’s being copied.
I know, probably in this concrete case the better strategy would have been to perform the data bootstrapping in the cloud when the user’s account is created for the first time by putting some kind of trigger that would execute the data generation code for only the new users. However, we are really trying to finalize realm cloud sync and release the app and we don’t have the capacity for that yet. Also, IMHO, if the data is not yet ready in the cloud, the app should not be allowed to sync any data to it. Is there a way to somehow wait on the app and restrict cloud sync if the cloud data copy is being executed?
Client reset exception infinite cycle
This one took us about a couple of weeks to find and reproduce. In our app we were experiencing an infinite client reset cycle, we would handle the client reset properly, and then after several seconds app would receive another client reset exception. Initially, we thought that we are not disposing of all of the Realm
instances. After a lot of “needle in a haystack” style search, we’ve find out that the cycle happens because we monitor for Upload
and Download
progress. (the way we do that can be found in this post). When we commented out the progress monitoring logic, everything worked just fine. Again, we started thinking that we did something wrong in our reactive chain and something gets leaked. But after some code filtration and extraction to the small sample I’ve attached, we discovered that just the bare directly-invoked progress monitoring logic results in this issue.
_bindings = new CompositeDisposable
{
_realm.SyncSession.GetProgressObservable(ProgressDirection.Download, ProgressMode.ReportIndefinitely).Subscribe(progress => DownloadProgress = (double)progress.TransferredBytes / progress.TransferableBytes),
_realm.SyncSession.GetProgressObservable(ProgressDirection.Upload, ProgressMode.ReportIndefinitely).Subscribe(progress => UploadProgress = (double)progress.TransferredBytes / progress.TransferableBytes)
};
We execute this logic during the launch of the app. In this small sample, we do that right after creating the realm instance. If we remove these lines from the sample, the client reset works just fine. If we add them, we will end up in a client reset loop. The only way to exit the loop is to force close the app and restart it. I think there’s a bug in the Realm .NET SDK which caches some connection under the hood and disposing of all of the realm instance doesn’t dispose that, causing this loop.
A workaround for this would be asking the user to force restart the app. However, I do hope that this bug can be addressed quickly, considering I have 100% reproducible sample.