Converting local realm to synced realm in the middle of app life cycle (in Swift)


#1

My app will have a paid feature called multi-devices sync. I would like to implement the feature with Realm Platform - Query Based Sync.

I know how to convert local realm to synced realm thanks to this thread.

But it’s based on the scenario that users sync their realm from the app start - before opening their non-synced local realm. That doesn’t work for me because my users will start sync when they paid for it.

Therefore, I have to convert their local realm in the middle of app life cycle and the local realm is already opened by that time.

My issue starts here. When I try to convert local realm to synced realm, app crashes with this message “Realm at path ‘…’ already opened with different read permissions”.

I tried to find a way to close local realm before converting it, but Realm cocoa does not allow me to close a realm programmatically.

Here’s my code converting local realm to synced realm.

func copyLocalRealmToSyncedRealm(user: RLMSyncUser) {
        
        let localConfig = RLMRealmConfiguration()
        localConfig.fileURL = Realm.Configuration.defaultConfiguration.fileURL
        localConfig.dynamic = true
        localConfig.readOnly = true
        
        // crashes here
        let localRealm = try! RLMRealm(configuration: localConfig)

        let syncConfig = RLMRealmConfiguration()
        syncConfig.syncConfiguration = RLMSyncConfiguration(user: user,
                                                            realmURL: realmURL,
                                                            isPartial: true,
                                                            urlPrefix: nil,
                                                            stopPolicy: .liveIndefinitely,
                                                            enableSSLValidation: true,
                                                            certificatePath: nil)
        syncConfig.customSchema = localRealm.schema
        
        let syncRealm = try! RLMRealm(configuration: syncConfig)
        syncRealm.schema = syncConfig.customSchema!
        try! syncRealm.transaction {
            let objectSchema = syncConfig.customSchema!.objectSchema
            for schema in objectSchema {
                let allObjects = localRealm.allObjects(schema.className)
                for i in 0..<allObjects.count {
                    let object = allObjects[i]
                    RLMCreateObjectInRealmWithValue(syncRealm, schema.className, object, true)
                }
            }
        }
    }

Any help will be appreciated.
Thanks.


#2

I posted this a week ago and it seems that nobody knows any solution for this.
This is a common use case though realm cloud is not ready for this?
Maybe there are fewer apps built with realm cloud than I thought and perhaps I’m the first one who try to sync the realm for the paid users only.


#3

It’s frustrating when you are trying to do a simple thing and you can’t get a response. You can always open a support ticket but before doing that, a few things to look at.

First, and not to rehash old topics but read Does anyone user Realm Cloud for production. The last note by @roberhofer is very important.

Second thing is the Github article you are following is two years old and is not current. The code isn’t correct. Realm has changed and updated just about all aspects of syncing in the last couple of years. We spent literally months trying to get a query based sync to work and never could - mostly due to incorrect and outdated documentation.

It’s better now so take a look at the current docs Using Sync’d Realms

Here’s a question; if you’re currently using a local realm, why are you doing this

let localRealm = try! RLMRealm(configuration: localConfig)

Can’t you just do this to get the default local realm?

// Get the default Realm
let realm = try! Realm()

Also… If you want to open a query based realm the code is something like this (from the current docs)

// Create the configuration
let syncServerURL = URL(string: "realms://myinstance.cloud.realm.io/~/userRealm")!
let config = user.configuration(realmURL: syncServerURL)

// Open the remote Realm
let realm = try! Realm(configuration: config)
// Any changes made to this Realm will be synced across all devices!

Lastly, I believe you need to be using SyncUser.configuration() for accessing the sync’d realm. Here’s a link to the SyncUser API


#4

Thank you for your reply.

The reason why I am trying to open a new local realm of type RLMRealm is Realm() and RLMRealm() has different properties.

If I use a default Realm which is already opened, I can’t access allObjects properties thus I can’t copy local realm to synced realm.

let allObjects = localRealm.allObjects(schema.className)

Also, Realm and RLMRealm both have schema property but they are different type so I can’t assign Realm.schema to RLMRealm.schema.

syncConfig.customSchema = localRealm.schema

I wish to know if there is a way to copy local realm to synced realm without creating a new RLMRealm().

PS. Here’s another one who suffered from the same issue. And it’s not clear that he somehow solved the issue. It seems that there’s no official solution for this issue.


#5

The use case here is kind of unclear. Why would you want to manually copy your local data to the sync server? That defeats the purpose of having a sync server as it does that for you automatically.

From the docs

When you open a realm on a device, the objects it contain will be replicated from the server to the device or vice versa, so that you have them available locally which means that you always have zero-latency access to them and that the app will keep working even if it goes online. Any changes done to the objects in the realm will eventually be reflected in the all other instances of the same realm on other devices.

If you’re using a sync’d realm you select what objects sync to the server and for those that do not, are only housed locally. In other words if you sync a subset of your data, then only that data is moved to the server. You can optionally sync all objects. Additionally you can have a query based sync or a full sync.

When opening the realm, you can choose to only replicate out a subset of the objects specified with a set of queries (Query Based Sync), or you can use full sync which circumvents the overhead of doing queries and just replicates the entire realm (Full Sync).

So, if you can have the data moved automatically why don’t you not sync the data, so it stays local, until you’re ready and then just turn on sync’ing which will then do what you asked:

copy local realm to synced realm


#6

I have been struggling with a similar and/or variation of this problem which I have posted about on this forum in the past.

My problem set can be summarized as:

  • Must provide a read-only global catalog of ~50k items that change regularly to every user
  • Allow users to browse the catalog anonymously until they decide to create an account
  • Keep their device(s) in sync with catalog changes when they occur on the server

Realm does not make it easy to implement a solution for this. I have found no way to bundle a pre-populated Realm file that can be opened by an anonymous user for syncing.

Initially, I tried relying entirely on sync to download the current catalog during the user’s first session but encountered the following issues:

  • Sync time; users complained about the initial download time of the catalog.
  • Since I don’t require a user account up front to use the app, I tried using anonymous logins but then had no ability to convert an anonymous login to a “real” login once a user explicitly created an account. Additionally, a “real” login is unable to open the Realm file originally created by an anonymous login.

I tried some of the copying hacks described in this thread and other resources I found without any luck so I abandoned that route. As such, I have been forced to bundle a synced Realm file with my app and use the same login to manage the syncing on every device where my app is installed. This works, but I hate that I have found no other way to solve this problem.


#7

I remember the request to pre-load a realm file with the app.
This does cause a bit of issues for us and thus we have not simple way to enable it.

Note though that we recently rolled out aSyncOpen functionality which speeds up initial download of the database. This capability will create a snapshot, compresses it server-side and then downloads this snapshot to the client. Then we only sync the diff between the snapshot (which can be slightly out of date) and the current state. Prior we synced from the beginning of time, which had a lot more overhead.

Thus I hope by using this methodology (which is supported for full sync, which seems applicable for your catalog case) the startup time will be much faster. We have seen from the first real-live use that a speed-up of 10x is possible.


#8

@roberhofer thank you for the response and the suggestions. A few follow up questions:

  • Is the asyncOpen you’re referring to the same as Realm.open or Realm.openAsync in the JavaScript package?
  • Is the initial download optimization only available via an async opener? I currently use new Realm(...) so that the app can still be used why synchronization is happening; would be nice to maintain this paradigm but still benefit from the optimizations you’ve described.
  • What is the recommended way to migrate from an anonymous user to a “real” user given my need to not require user accounts up front? I’d prefer not to have to close the anonymous realm and resync again once the user creates a permanent account.

#9

Hi @kenniejaydavis:

  • The asyncOpen is referring to Realm.open (see here in the doc). Realm.openAsync is going to be deprecated.
  • The download optimization can only work in the context of aSyncOpen, not creating a new (synchronous) Realm. The speed-up relies on the download of a full snapshot, and you can’t write to a snapshot while it is in the middle of being downloaded…
  • Unfortunately we do not have a graceful way to migrate an anonymous user to a full user or form a local realm to a synced realm. The former requires re-sync, the latter for you to write migration functions. We know that we have to provide some convenience functions to simplify this. I do hope that with the additional resources becoming available we can start to tackle some of the rough remaining edges.