Converting Local Realms to Synced Realm

How to upgrade a local realm to a synced realm?

After reading the documentation, https://docs.mongodb.com/realm/sdk/ios/examples/sync-changes-between-devices/#open-a-synced-realm-offline. The only option available is to manually copy the data from the local realm to the newly created synced realm after user sign up.

You can copy data from a local Realm Database to a synced realm, but you cannot sync a local Realm Database. You must initialize a synced realm with a sync configuration.

Sample Image

A similar question was asked in the MongoDB community "Upgrading a Default Realm to a Synced Realm and Vice Versa" and a response from the MongoDB team on 5th June 2021 was copying data between sync and non-sync realms is the only option.

To copy data from one realm to another on iOS, How to copy objects from the default local realm to a MongoDB synced realm in iOS?

Relevant Info

  • realm-js - Copy local realm data to sycned realm

  • Copy all objects from one Realm to another

  • Asynchronously copy from one realm to another

Converting local Realms to synced Realms: 'customSchema' is inaccessible

With no better option, I decided to use Objective-C in my Swift project. So, I added a SWIFT_OBJC_BRIDGING_HEADER (Xcode does that automatically when you add an Objective-C file) and created a RealmConverter object:

RealmConverter.h

#import <Foundation/Foundation.h>

@import Realm;

NS_ASSUME_NONNULL_BEGIN

@interface RealmConverter : NSObject

- (void)convertLocalToSyncRealm:(NSURL *)server local:(NSURL *)local username:(NSString *)username password:(NSString *)password completion:(void (^)(NSError * _Nullable))completion;

@end

NS_ASSUME_NONNULL_END

RealmConverter.m

#import "RealmConverter.h"

@import Realm.Dynamic;
@import Realm.Private;

@implementation RealmConverter

- (void)convertLocalToSyncRealm:(NSURL *)server local:(NSURL *)local username:(NSString *)username password:(NSString *)password completion:(void (^)(NSError * _Nullable))completion {
RLMRealmConfiguration *configuration = [[RLMRealmConfiguration alloc] init];
configuration.fileURL = local;
configuration.dynamic = true;
configuration.readOnly = YES;

RLMRealm *localRealm = [RLMRealm realmWithConfiguration:configuration error:nil];

RLMSyncCredentials *credentials = [RLMSyncCredentials credentialsWithUsername:username password:password register:YES];
[RLMSyncUser logInWithCredentials:credentials authServerURL:server onCompletion:^(RLMSyncUser *syncUser, NSError *error) {
if (error) {
completion(error);
return;
}

dispatch_async(dispatch_get_main_queue(), ^{
RLMRealmConfiguration *syncConfig = [[RLMRealmConfiguration alloc] init];
syncConfig.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:syncUser realmURL:[NSURL URLWithString:[NSString stringWithFormat:@"realm://%@:%@/~/<redacted>", server.host, server.port]]];
syncConfig.customSchema = [localRealm.schema copy];

RLMRealm *syncRealm = [RLMRealm realmWithConfiguration:syncConfig error:nil];
syncRealm.schema = syncConfig.customSchema;

NSError *error = nil;
[syncRealm transactionWithBlock:^{
NSArray *objectSchema = syncConfig.customSchema.objectSchema;
for (RLMObjectSchema *schema in objectSchema) {
RLMResults *allObjects = [localRealm allObjects:schema.className];
for (RLMObject *object in allObjects) {
RLMCreateObjectInRealmWithValue(syncRealm, schema.className, object, true);
}
}
completion(nil);
} error:&error];

if (error) {
completion(error);
}
});
}];
}

@end

Then add #import "RealmConverter.h" to your bridging header and then use it in your Swift code like:

RealmConverter().convertLocal(toSyncRealm: URL(string: "http://localhost:9080")!, local: Realm.Configuration.defaultConfiguration.fileURL!, username: "user@example.com", password: "12345678") { error in
print("Done:", error ?? "nil")
}

Switch from local to synced Realm

This is possible to do, but you cannot automatically convert a standalone Realm into a synchronized Realm (in the future this could change). Instead, you would create a second synchronized Realm and then just copy the data from the standalone Realm into it.

As for requiring a login, currently to open a synchronized Realm you need an authorized User and the synced Realm URL. To get an authorized User, you must login with various credential mechanisms: username/password, Google, and Facebook are currently supported.

What it sounds like you need is an "anonymous" User, where Realm Object Server would generate an authorized User without a login. We plan to add this functionality in a later version and support for adding credentials to a User. This means that you could open a synchronized Realm immediately without a login via an "anonymous" User then later ask the end user to login, attaching actual credentials to the User object, such that if the end user used another device, they could login and identify themselves with the same underlying User object.

How to copy objects from the default local realm to a MongoDB synced realm using Swift?

Update

Found a potential workaround for this issue in "How can I easily duplicate/copy an existing realm object" using Codable with JSONEncoder and JSONDecoder, https://stackoverflow.com/a/65436546/2226315

Found another function in GitHub Gist migrates local realm to a synced realm, however, the code seems already outdated.


Found a GitHub issue raised in 2014 about how to move/copy objects between realms, https://github.com/realm/realm-cocoa/issues/1076. createInRealm:withObject: was suggested in the comment.

However, after digging into the source code only createInRealm:withValue: is available in Objective-C API, https://github.com/realm/realm-cocoa/blob/84179b4e0b54e11f73723c283c4567c565da62f5/Realm/RLMObject.h#L174

In the official guide on "Add Sync to a Local-Only App", there is a section about "Copy Existing Data" which outlines the flow to copy data from a local realm to a synced realm.

Sample Image

Relevant Info

  • Converting local realms to synced realm
  • https://github.com/realm/realm-cocoa/issues/5381

Exception when converting local realm to synced realm in Xamarin.iOS

You need to create a non-managed copy of your RealmObject subsclass before adding it to the new Realm.

You can do this manually or via a helper extension.

Via NonManagedCopy extension from RealmJson.Extensions(1):

var nonSyncedRealm = Realm.GetInstance(nonSyncedRealmConfig);
var syncedRealm = Realm.GetInstance(syncedRealmConfig);

var nonSyncedAll = nonSyncedRealm.All<ARealmClass>();
syncedRealm.Write(() =>
{
foreach (var realmObject in nonSyncedAll)
{
var syncedObject = realmObject.NonManagedCopy<ARealmClass>();
syncedRealm.Add(syncedObject, true);
}
});

Manual Copy:

Assign each property of your RealmObject subclass to a newly instanced non-managed object.

syncedRealm.Write(() =>
{
foreach (var realmObject in nonSyncedAll)
{
var syncedObject = new ARealmClass
{
Id = realmObject.Id,
Name = realmObject.Name,
~~~~~~
~~~~~~
~~~~~~
~~~~~~
};
syncedRealm.Add(syncedObject, true);
}
});

(1) Disclaimer RealmJson.Extensions is an extension I wrote, available via nuget package or source:

re: https://sushihangover.github.io/Realm.Json.Extensions/

Realm use local db instead of sync when network connection is unavailable?

The way Realm Sync works is by ensuring that the copy of a Realm on your device and the copy of that same Realm on the server (and on any other devices) are always kept synchronized with each other. This means that you can use a synced Realm in exactly the same way as any other Realm even when there is no network connectivity at all. The synced Realm is the local Realm; you do not need to manually maintain two separate Realms containing the same data.

If you are offline when the app launches you can use the SyncUser.current API to get the previously logged-in user so you can open your synced Realms. (If you have logged in multiple users previously, you must use the SyncUser.all API instead to get a list of all the users and pick the one you want to use.)



Related Topics



Leave a reply



Submit