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

484 views Asked by At

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

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

But this is 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 comes in 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

There are 2 answers

0
kprater On

I made a copy of the local realm file and opened the copy with RLMRealmConfiguration. Afterwards, just delete both files. It is not the best solution, but it works

0
BPDev On

I think the best approach is to have 2 realms. In my case, I am using SwiftUI. If a user is logged in, I use a different configuration.

struct ContentView: View {
    
    @ObservedObject var app = Realm.app
    
    var body: some View {
        if let config = app.createFlexibleConfiguration() {
            MainScreenSync()
                .environment(\.realmConfiguration, config)
                .environmentObject(app)
        } else {
            MainScreen()
                .environmentObject(app)
        }
    }
}

Once SwiftUI is using the synced realm, I open the local realm as well.

struct MainScreenSync: View{
    @EnvironmentObject var app: RealmSwift.App
    //    @Environment(\.realm) var syncedRealm
    @ObservedResults(Item.self) var syncedItems
    
    var body: some View {
        VStack {
            MainScreen()
            Text(app.currentUser?.description ?? "not logged in")
        }
        .onAppear {
            if let localRealm = try? Realm(), let user = app.currentUser {
                let localItems = localRealm.objects(Item.self)
                for item in localItems {
                    // local -> synced
                    let syncedItem = Item(value: item)
                    syncedItem.userId = user.id
                    $syncedItems.append(syncedItem)
                    // delete local
                    try? localRealm.write {
                        localRealm.delete(item)
                    }
                }
            }
        }
    }
}

link

There is probably a cleaner way to do it, but I simply copy items from the local realm, add a user id, write to the synced realm and then delete from the local realm.

There is a page in the documentation explaining edge cases. I wish they would handle it for us.