Syncing Data

7
Last updated yesterday

Overview

This section we'll learn the basics for syncing and reading data depending on which type of sync we've chosen. After this, we'll discuss some of the ways that you can monitor sync progress over the network, and finally, we'll learn some of the rules around schema changes. To do this, you'll need have successfully authenticated with your server, decided on your type of sync, and created a Realm. If any of this is unfamiliar with you, check out the following sections:

Query-based synchronization

When using Query-based sync, a developer chooses a "reference" Realm from the server which serves as the master Realm Database. From this Realm, subscription queries determine which subset of the data gets synced down to the client.

Subscribing to data (Reading Data)

By default, a query-based synchronized Realm contains no data. Instead, the client application must choose, or subscribe to, which subset of data in the corresponding Realm on the server it wants to synchronize.

Subscribing to data is easy, as it utilizes Realm's query system. Applications can create any number of data queries, which will be transmitted to the server and evaluated. The query results will then be synced to the application. The underlying sync protocol ensures that if an object matches several queries an application has subscribed to, the server will only send that object once.

Subscriptions are automatically persisted and maintained by the server. When data changes occur the server will reevaluate existing subscriptions and push the changes to all subscribing clients.

Swift
Objective-C
Java
Javascript
.Net
let results = realm.objects(Person.self).filter("age > 18")
let subscription = results.subscribe()

subscribe() registers the query with the server, and returns a SyncSubscription object which can be used to observe the current state of the subscription or to remove it. A subscription can also be given an explicit name by passing the desired name to subscribe():

let subscription = results.subscribe(named: "my-subscription")
RLMSyncSubscription *subscription =
[[Person objectsInRealm:realm where:@"age > 17"] subscribe];

subscribe registers the query with the server, and returns anRLMSyncSubscription object which can be used to observe the current state of the subscription or to remove it. A subscription can also be given an explicit name by passing the desired name to subscribeWithName::

RLMSyncSubscription *subscription =[[Person objectsInRealm:realm where:@"age > 17"]
subscribeWithName:@"my-subscription"];
realm.where(Person.class)
.greaterThanOrEqual("age", 18)
.findAllAsync();

The server automatically turns all asynchronous queries into subscriptions. If .findAllAsync is left null, the server will turne the query into an anonymous subscription, which cannot be unregistered and will always be running.

To create a named subscriptions that can be unregistered again specify a include a .findAllAsync value.

realm.where(Person.class)
.greaterThanOrEqual("age", 18)
.findAllAsync("my-subscription");
let results = realm.objects('Person').filtered('age >= 18');
let subscription = results.subscribe();
var subscription = realm.All<Person>().Where(p => p.Age > 18).Subscribe();

Subscribe() registers the query with the server, and returns a Subscription object which can be used to observe the current state of the subscription or to remove it. A subscription can also be given an explicit name by passing the desired name to Subscribe():

var subscription = realm.All<Person>().Where(p => p.Age > 18).Subscribe("my-subscription");

Limiting subscriptions

When using Query-based Realms, data will be fetched from the server when queried for. In this case it can make sense to limit the number of results as it directly impact how much data is transferred from the server.

This is done by specifying a limit on the query itself the following way.

Swift
Objective-C
Java
Javascript
.Net
let subscription = realm.objects(Person.self)
.sorted(byKeyPath: "score")
.subscribe(limit: 10)
RLMResults *results = [Person objectsInRealm:realm];
RLMSyncSubscription *subscription = [results subscribeWithName:@"query" limit:10];
RealmResults<Person> people = realm.where(Person.class)
.sort("score")
.limit(10)
.findAllAsync();

The keywords distinct(), sort() and limit() will be applied in the order they are specified. Depending on the data set this can make a difference to the query result. Generally, limit() should be applied last.

let subscription = realm.objects('Person')
.filtered('SORT(score ASC) LIMIT 10')
.subscribe();
// Not supported by .NET yet

Limited query results are auto-updated like any other query results. This means that if 10 elements are returned when the query first returns, these 10 objects can be replaced or removed as the underlying data set changes.

When using fine-grained notifications, objects that stops being part of the query result will be reported as deleted. This does not necessarily mean that they are deleted from the underlying Realm, just that they are no longer part of the query result.

Offsetting limited query results is not supported yet. If you want to implement paging you should sort the result based on a property and request the next page by using a combination of a limit and these property values.

Registering for notifications

Working with a synced Realm is no different than a local Realm (other than it is automatically synchronized). As a result, you can utilize Realm's existing notification functionality to register for changes that occur through synchronization.

The notification system is critical when using Query-based synchronization because you will need to know when the server has fulfilled the initial subscription, in addition to when later data changes have occurred.

As a general rule of thumb, you'll want to use the subscription state to track your initial download of the data after creating a subscription. You can then use a results notification to react to changes made in the data. Both are shown in the code snippets below.

Swift
Objective-C
Java
Javascript
.Net
let results = realm.objects(Person.self).filter("age > 18")
let subscription = results.subscribe()
let subscriptionToken = subscription.observe(\.state) { state in
switch state {
case .creating:
// The subscription has not yet been written to the Realm
case .pending:
// The subscription has been written to the Realm and is waiting
// to be processed by the server
case .complete:
// The subscription has been processed by the server and all objects
// matching the query are in the client Realm
case .invalidated:
// The subscription has been removed
case .error(let err):
// An error occurred while processing the subscription
}
let resultsToken = results.observe() { changes in
// Called whenever the objects in the client Realm which match the query
// change, including when a subscription being added or removed changes
// which objects are included.
}
RLMResults *results = [Person objectsInRealm:realm where:@"age > 17"];
RLMSyncSubscription *subscription = [results subscribe];
RLMNotificationToken *token = [results addNotificationBlock:^(RLMResults<Person *> *results, RLMCollectionChange *changes, NSError *error) {
switch (subscription.state)
{
case RLMSyncSubscriptionStateCreating:
// The subscription has not yet been written to the Realm
break;
case RLMSyncSubscriptionStatePending
// The subscription has been written to the Realm and is waiting
// to be processed by the server
break;
case RLMSyncSubscriptionStateComplete
// The subscription has been processed by the server and all objects
// matching the query are in the local Realm
break;
case RLMSyncSubscriptionStateInvalidated
// The subscription has been removed
break;
case RLMSyncSubscriptionStateError:
// An error occurred while processing the subscription
break;
default:
break;
}
}];
RealmResults<Person> persons = realm.where(Person.class).greaterThanOrEqual("age", 18).findAllAsync();
query.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<Person>>() {
@Override
public void onChange(RealmResults<Person> persons, OrderedCollectionChangeSet changeSet) {
if (changeSet.isCompleteResult()) {
// If true, data has been downloaded from the server.
// If false, the query result is only based on local
// data.
}
}
});
let results = realm.objects('Person').filtered('age >= 18');
let subscription = results.subscribe();
subscription.addListener((sub, state) => {
switch (state) {
case Realm.Sync.SubscriptionState.Creating
// The subscription has not yet been written to the Realm
break;
case Realm.Sync.SubscriptionState.Pending
// The subscription has been written to the Realm and is waiting
// to be processed by the server
break;
case Realm.Sync.SubscriptionState.Complete:
// The subscription has been processed by the server and all objects
// matching the query are in the local Realm
break;
case Realm.Sync.SubscriptionState.Invalidated
// The subscription has been removed
break;
case Realm.Sync.SubscriptionState.Error:
console.log('An error occurred: ', subscription.error);
break;
}
});

You can also listen to changes to the results set as usual:

let results = realm.objects('Person').filtered('age >= 18');
let subscription = results.subscribe();
results.addListener((collection, changes) => {
// Called whenever the objects in the local Realm which match the query
// change, including when a subscription being added or removed changes
// which objects are included
});
var subscription = realm.All<Person>().Where(p => p.Age > 18).Subscribe();
// The Subscription class implements INotifyPropertyChanged so you can
// pass it directly to the data-binding engine
subscription.PropertyChanged += (s, e) =>
{
switch (subscription.State)
{
case SubscriptionState.Creating:
// The subscription has not yet been written to the Realm
break;
case SubscriptionState.Pending:
// The subscription has been written to the Realm and is waiting
// to be processed by the server
break;
case SubscriptionState.Complete:
// The subscription has been processed by the server and all objects
// matching the query are in the local Realm
break;
case SubscriptionState.Invalidated:
// The subscription has been removed
break;
case SubscriptionState.Error:
// An error occurred while processing the subscription
var error = subscription.Error;
break;
}
};
subscription.Results.AsRealmCollection().CollectionChanged += (s, e) =>
{
// Called whenever the objects in the local Realm which match the query
// change, including when a subscription being added or removed changes
// which objects are included.
};

Unsubscribing

If a client does not need the data from a subscription anymore, it can choose to unsubscribe. This does not delete the objects from the server, but instead simply removes them from the client device. For example, if an application provides user-driven search (which creates a subscription), it might choose to keep only the results from the most recent query in order to limit data stored on the device.

Swift
Objective-C
Java
Javascript
.Net
let subscription = realm.objects(Person.self).filter("age > 18").subscribe()
subscription.unsubscribe()
RLMSyncSubscription *subscription =
[[Person objectsInRealm:realm where:@"age > 17"] subscribe];
[subscription unsubscribe];
realm.unsubscribeAsync("my-subscription", new Realm.UnsubscribeCallback() {
@Override
public void onSuccess(String subscriptionName) {
// Succesfully unsubscribed
}
@Override
public void onError(String subscriptionName, Throwable error) {
// Something went wrong when unsubscribing
}
});
results.unsubscribe();
var subscription = realm.All<Person>().Where(p => p.Age > 18).Subscribe();
await subscription.UnsubscribeAsync();
// Alternatively, for named subscriptions, you can also unsubscribe
// by their name:
var named = realm.All<Person>().Where(p => p.Age > 18).Subscribe("legal-drivers");
await Subscription.UnsubscribeAsync("legal-drivers");

Finding existing subscriptions

Swift
Objective-C
Java
Javascript
.Net

It is currently not possible to lookup existing subscriptions in Swift. For now it is recommended to name all subscriptions and save the name outside Realm.

let subscription = results.subscribe(named: "my-subscription")

It is currently not possible to lookup existing subscriptions in Objective-C. For now it is recommended to name all subscriptions and save the name outside Realm.

RLMSyncSubscription *subscription =[[Person objectsInRealm:realm where:@"age > 17"]
subscribeWithName:@"my-subscription"];

You can look up a single, existing, subscription by name.

Subscription sub = realm.getSubscription("my-subscription");

Or find all subscriptions that match a given pattern. * and ? can be used as wildcard placeholders meaning either "All characters" or "Single character"

// Find all subscriptions
RealmResults<Subscription> subs = realm.getSubscriptions();
// Find all subscriptions starting with "screenA."
RealmResults<Subscription> subs = realm.getSubscriptions("screenA.*");
// Fund all subscriptions that ends with a single letter.
// Like "screenA.page1", "screenA.page2" and so on.
RealmResults<Subscription> subs = realm.getSubscriptions("screenA.page?");

Use Realm.subscriptions() to search for any existing subscriptions. It is also possible to search for subscriptions by name or those that matches a given pattern. * and ? can be used as wildcard placeholders meaning either "All characters" or "Single character"

// Find all subscriptions
let subscriptions = realm.subscriptions();
// Find all subscriptions starting with "screenA."
let subscriptions = realm.subscriptions("screenA.*");
// Fund all subscriptions that ends with a single letter.
// Like "screenA.page1", "screenA.page2" and so on.
let subscriptions = realm.subscriptions("screenA.page?");

It is currently not possible to lookup existing subscriptions in .NET. For now it is recommended to name all subscriptions and save the name outside Realm.

var subscription = realm.All<Person>()
.Where(p => p.Age > 18)
.Subscribe("my-subscription");

Creating initial subscriptions

Query-based Realms start out completely empty, and data are then fetched as needed. However, in some cases there are a set of data that is needed from the server before the Realm is meaningfully ready to use.

In that case you can define the initial set of subscriptions up front, and have them downloaded before the Realm is opened.

Swift
Objective-C
Java
Javascript
.Net

Swift currently doesn't expose any API's for defining the initial set of subscriptions. Use the normal way of subscribing for data after the Realm has been opened.

Objective-C currently doesn't expose any API's for defining the initial set of subscriptions. Use the normal way of subscribing for data after the Realm has been opened.

In order to define initial subscriptions, create them in the initalData block and enable the waitForInitialRemoteData mode. This setting will respect any Subscriptions created in the initialData block and not report the Realm ready before the data has been downloaded.

// Create the configuration
SyncUser user = login();
String url = getUrl();
SyncConfiguration config = user.createConfiguration(url)
.initialData(realm -> {
realm.where(Person.class).subscribe();
})
.waitForInitialRemoteData(30, TimeUnit.SECONDS)
.build();
// Use Realm.getInstanceAsync() if on the UI thread
Realm.getInstanceAsync(config, new Realm.Callback() {
@Override
public void onSuccess(Realm realm) {
// Realm is ready
}
});
// or use Realm.getInstance() if on a background thread that is allowed
// to block until the Realm is downloaded.
Realm realm = Realm.getInstance(config);

Javascript currently doesn't expose any API's for defining the initial set of subscriptions. Use the normal way of subscribing for data after the Realm has been opened.

.NET currently doesn't expose any API's for defining the initial set of subscriptions. Use the normal way of subscribing for data after the Realm has been opened.

Writing Data

Writing data to a synchronized Realm is done the same way as writing data to a local Realm.

Swift
Objective-C
Java
Javascript
.Net
try! realm.write {
realm.add(Person(value: ["name" : "Jane", "age": 21]))
}
[realm beginWriteTransaction];
Person *person = [[Person alloc] initWithValue:@{@"name" : @"Jane", @"age" : @21}];
[realm addObject:person];
[realm commitWriteTransaction];
realm.executeTransaction(r -> {
r.insert(new Person("Jane", 21));
});
realm.write(() => {
realm.create('Person', {
name: 'Jane',
age: 21
});
});
realm.Write(() =>
{
var person = new Person();
myDog.Name = "Jane";
myDog.Age = 21;
realm.Add(person);
});

Full Synchronization

When you open a Full-sync Realm, its entire contents will be synchronized automatically in the background. You will not need to use the subscription APIs described in Reading & Writing data, Query-based . If you want to control what data is synchronized with a client, you will need to design your database as multiple, separate Realms. For example, a common pattern is to use a global Realm for shared data at a base path /globalRealm and put user-specific data in an individual Realm at each user's scoped path: /~/myRealm.

Reading data

Reading data from a fully synchronized Realm is just like reading data from a local Realm. However, since the Realm is automatically synchronized in the background it is highly recommend to register a notification so your UI can react to changes from the server.

Swift
Objective-C
Java
Javascript
.Net
let results = realm.objects(Person.self).filter("age > 18")
let resultsToken = results.observe() { changes in
// Called whenever the objects in the Realm have changed.
// This change could both be local or come from the server.
}
__weak typeof(self) weakSelf = self;
self.notificationToken = [[Person objectsWhere:@"age > 18"]
addNotificationBlock:^(RLMResults<Person *> *results, RLMCollectionChange *changes, NSError *error) {
// Called whenever the objects in the Realm have changed.
// This change could both be local or come from the server.
}];
RealmResults<Person> persons = realm.where(Person.class)
.greaterThanOrEqual("age", 18)
.findAllAsync();
query.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<Person>>() {
@Override
public void onChange(RealmResults<Person> persons, OrderedCollectionChangeSet changeSet) {
// Called whenever the objects in the Realm have changed.
// This change could both be local or come from the server.
}
});
let results = realm.objects('Person').filtered('age >= 18');
// TODO

You can also listen to changes to the results set as usual:

let results = realm.objects('Person').filtered('age >= 18');
results.addListener((objects, changes) => {
// Called whenever the objects in the local Realm which match the query
// change, including when a subscription being added or removed changes
// which objects are included
});
var results = realm.All<Person>().Where(p => p.Age > 18);
var token = results.SubscribeForNotifications ((sender, changes, error) =>
{
// Called whenever the objects in the Realm have changed.
// This change could both be local or come from the server.
});

If you want to ensure that a Realm is fully up to date with the server then you can register a Progress Notification.

It is not possible to tell local changes apart from remote changes. They will all trigger the notifications the same way.

Writing data

Writing data to a synchronized Realm is done the same way as writing data to a local Realm.

Swift
Objective-C
Java
Javascript
.Net
try! realm.write {
realm.add(Person(value: ["name" : "Jane", "age": 21]))
}
[realm beginWriteTransaction];
Person *person = [[Person alloc] initWithValue:@{@"name" : @"Jane", @"age" : @21}];
[realm addObject:person];
[realm commitWriteTransaction];
realm.executeTransaction(r -> {
r.insert(new Person("Jane", 21));
});
realm.write(() => {
realm.create('Person', {
name: 'Jane',
age: 21
});
});
realm.Write(() =>
{
var person = new Person();
myDog.Name = "Jane";
myDog.Age = 21;
realm.Add(person);
});

If you want to ensure that a change has been uploaded to the server, you can register a Progress Notification.

Monitoring Sync Progress

When you open a synchronized Realm, it will establish a network connection with the server in the background. Realm Platform uses Websockets with a custom binary protocol designed to be highly efficient for real-time performance. The Cloud version uses SSL/TLS by default, whereas with the Self-Hosted version this requires setup.

Sync Session

The client APIs provide access to the underlying networking through a session object. This can be used to query for the networking state.

Swift
Objective-C
Java
Javascript
.Net

Session objects representing Realms opened by a specific user can be retrieved from that user’s SyncUser object using the SyncUser.allSessions() or SyncUser.session(for:) APIs.

To determine the session's connection state to the realm object server you can access the SyncSession.ConnectionState which returns Disconnect, Connecting, or Connected.

The overall state of the underlying session can be retrieved using the State property. This can be used to check whether the session is active, inactive, or in an error state. If the session is valid, the configuration property will contain a SyncConfiguration value that can be used to open another instance of the same Realm (for example, on a different thread).

A synced Realm’s connection to the Realm Object Server is represented by a RLMSyncSession object. Session objects representing Realms opened by a specific user can be retrieved from that user’s RLMSyncUser object using the +[RLMSyncUser allSessions] or -[RLMSyncUser sessionForURL:] APIs.

The state of the underlying session can be retrieved using the state property. This can be used to check whether the session is active, invalid, or in an error state. If the session is valid, the configuration property will contain a RLMSyncConfiguration value that can be used to open another instance of the same Realm (for example, on a different thread).

A synced Realm’s connection to the Realm Object Server is represented by a SyncSession object. A session object for a specific Realm can be retrieved by using the SyncManager.getSession(SyncConfiguration) API.

The SyncSession object can call getConnectionState which will return Disconnected, Connecting, or Connected.

A synced Realm’s connection to the Realm Object Server is represented by a Session object. Session objects can be retrieved by calling realm.syncSession.

The connection state of syncSession can be seen through .connectionState. This will return Disconnect, Connecting, or Connected.

The state of the underlying session can be retrieved using the state property. This can be used to check whether the session is active, invalid, or in an error state.

A synced Realm’s connection to the Realm Object Server is represented by a Session object. Session objects can be retrieved by calling realm.GetSession().

The state of the underlying session can be retrieved using the State property. This can be used to check whether the session is active or inactive.

Progress Notifications

Swift
Objective-C
Java
Javascript
.Net

Session objects allow your app to monitor the status of a session’s uploads to and downloads from the Realm Object Server by registering progress notification blocks on a session object.

Progress notification blocks will be invoked periodically by the synchronization subsystem on the runloop of the thread in which they were originally registered. If no runloop exists, one will be created. (Practically speaking, this means you can register these blocks on background queues in GCD and they will work fine.) As many blocks as needed can be registered on a session object simultaneously. Blocks can either be configured to report upload progress or download progress.

Each time a block is called, it will receive the current number of bytes already transferred, as well as the total number of transferrable bytes (defined as the number of bytes already transferred plus the number of bytes pending transfer).

When a block is registered, the registration method returns a token object. The invalidate() method can be called on the token to stop notifications for that particular block. If the block has already been deregistered, calling the stop method does nothing. Note that the registration method might return a nil token if the notification block will never run again (for example, because the session was already in a fatal error state, or there is no further progress to report).

There are two types of blocks. Blocks can be configured to report indefinitely. These blocks will remain active unless explicitly stopped by the user and will always report the most up-to-date number of transferrable bytes. This type of block could be used to control a network indicator UI that, for example, changes color or appears only when uploads or downloads are actively taking place.

let session = SyncUser.current!.session(for: realmURL)!
let token = session.addProgressNotification(for: .download,
mode: .reportIndefinitely) { progress in
if progress.isTransferComplete {
hideActivityIndicator()
} else {
showActivityIndicator()
}
}
// Much later...
token?.invalidate()

Blocks can also be configured to report progress for currently outstanding work. These blocks capture the number of transferrable bytes at the moment they are registered and always report progress relative to that value. Once the number of transferred bytes reaches or exceeds that initial value, the block will automatically unregister itself. This type of block could, for example, be used to control a progress bar that tracks the progress of an initial download of a synced Realm when a user signs in, letting them know how long it is before their local copy is up-to-date.

let session = SyncUser.current!.session(for: realmURL)!
var token: SyncSession.ProgressNotificationToken?
token = session.addProgressNotification(for: .upload,
mode: .forCurrentlyOutstandingWork) { progress in
updateProgressBar(fraction: progress.fractionTransferred)
if progress.isTransferComplete {
hideProgressBar()
token?.invalidate()
}
}

Session objects allow your app to monitor the status of a session’s uploads to and downloads from the Realm Object Server by registering progress notification blocks on a session object.

Progress notification blocks will be invoked periodically by the synchronization subsystem on the runloop of the thread in which they were originally registered. If no runloop exists, one will be created. (Practically speaking, this means you can register these blocks on background queues in GCD and they will work fine.) As many blocks as needed can be registered on a session object simultaneously. Blocks can either be configured to report upload progress or download progress.

Each time a block is called, it will receive the current number of bytes already transferred, as well as the total number of transferrable bytes (defined as the number of bytes already transferred plus the number of bytes pending transfer).

When a block is registered, the registration method returns a token object. The -invalidate method can be called on the token to stop notifications for that particular block. If the block has already been deregistered, calling the stop method does nothing. Note that the registration method might return a nil token if the notification block will never run again (for example, because the session was already in a fatal error state, or there is no further progress to report).

There are two types of blocks. Blocks can be configured to report indefinitely. These blocks will remain active unless explicitly stopped by the user and will always report the most up-to-date number of transferrable bytes. This type of block could be used to control a network indicator UI that, for example, changes color or appears only when uploads or downloads are actively taking place.

RLMSyncSession *session = [[RLMSyncUser currentUser] sessionForURL:realmURL];
void(^workBlock)(NSUInteger, NSUInteger) = ^(NSUInteger downloaded, NSUInteger downloadable) {
if ((double)downloaded/(double)downloadable >= 1) {
[viewController hideActivityIndicator];
} else {
[viewController showActivityIndicator];
}
};
RLMProgressNotificationToken *token;
token = [session addProgressNotificationForDirection:RLMSyncProgressDirectionDownload
mode:RLMSyncProgressModeReportIndefinitely
block:workBlock];
// Much later...
[token invalidate];

Blocks can also be configured to report progress for currently outstanding work. These blocks capture the number of transferrable bytes at the moment they are registered and always report progress relative to that value. Once the number of transferred bytes reaches or exceeds that initial value, the block will automatically unregister itself. This type of block could, for example, be used to control a progress bar that tracks the progress of an initial download of a synced Realm when a user signs in, letting them know how long it is before their local copy is up-to-date.

RLMSyncSession *session = [[RLMSyncUser currentUser] sessionForURL:realmURL];
RLMProgressNotificationToken *token;
void(^workBlock)(NSUInteger, NSUInteger) = ^(NSUInteger uploaded, NSUInteger uploadable) {
double progress = ((double)uploaded/(double)uploadable);
progress = progress > 1 ? 1 : progress;
[viewController updateProgressBarWithFraction:progress];
if (progress == 1) {
[viewController hideProgressBar];
[token invalidate];
}
};
token = [session addProgressNotificationForDirection:RLMSyncProgressDirectionUpload
mode:RLMSyncProgressModeForCurrentlyOutstandingWork
block:workBlock];

Session objects allow your app to monitor the status of a session’s uploads to and downloads from the Realm Object Server by registering progress notifications on a session object.

Progress notifications are invoked periodically by the synchronization subsystem. The notification callback will happen on a worker thread, so manipulating UI elements must be done using Activity.runOnUiThreador similar. As many notifications as needed can be registered on a session object simultaneously. Notifications can either be configured to report upload progress or download progress.

Each time a notification is called, it will receive the number of bytes already transferred, as well as the total number of transferrable bytes (the number of bytes already transferred plus the number of bytes pending transfer).

Notifications can be un-registered by using SyncSession.removeProgressListener.

There are two types of notification modes: ProgressMode.CURRENT_CHANGES and ProgressMode.INDEFINITELY. Notifications configured with ProgressMode.INDEFINITELY will remain active unless explicitly stopped by the user and will always report the most up-to-date number of transferrable bytes. This type of block could be used to control a network indicator UI that, for example, changes color or appears only when uploads or downloads are actively taking place. A notification in this mode can report Progress.isTransferComplete multiple times.

ProgressListener listener = new ProgressListener() {
@Override
public void onChange(Progress progress) {
activity.runOnUiThread(new Runnable) {
@Override
public void run() {
if (progress.isTransferComplete()) {
hideActivityIndicator();
} else {
showActivityIndicator();
}
}
}
}
};
SyncSession session = SyncManager.getSession(getSyncConfiguration());
session.addDownloadProgressListener(ProgressMode.INDEFINITELY, listener);
// When stopping activity
session.removeProgressListener(listener);

Notifications configured with ProgressMode.CURRENT_CHANGES only report progress for currently outstanding work. These notifications capture the number of transferrable bytes at the moment they are registered and always report progress relative to that value. Once the number of transferred bytes reaches or exceeds that initial value, the notification will send one final event where Progress.isTransferComplete is true and then never again. The listener should be unregistered at this point. This type of notification could, for example, be used to control a progress bar that tracks the progress of an initial download of a synced Realm when a user signs in, letting them know when their local copy is up-to-date.

final SyncSession session = SyncManager.getSession(getSyncConfiguration());
ProgressListener listener = new ProgressListener() {
@Override
public void onChange(Progress progress) {
activity.runOnUiThread(new Runnable) {
@Override
public void run() {
if (progress.isTransferComplete()) {
hideProgressBar();
session.removeProgressListener(this);
} else {
updateProgressBar(progress.getFractionTransferred());
}
}
}
}
};
setupProgressBar();
session.addDownloadProgressListener(ProgressMode.CHANGES_ONLY, listener);

Session objects allow your app to monitor the status of a session’s uploads to and downloads from the Realm Object Server by registering a progress notification callback on a session object by calling [realm.syncSession.addProgressNotification(direction, mode, callback)] or one of the asynchronous methods Realm.open and Realm.openAsync(these methods support only a subset of the progress notifications modes).

Progress notification callbacks will be invoked periodically by the synchronization subsystem. As many callbacks as needed can be registered on a session object simultaneously. Callbacks can either be configured to report upload progress or download progress.

Each time a callback is called, it will receive the current number of bytes already transferred, as well as the total number of transferable bytes (defined as the number of bytes already transferred plus the number of bytes pending transfer).

To stop receiving progress notifications a callback can be unregistered using [realm.syncSession.removeProgressNotification(callback)]. Calling the function a second time with the same callback is ignored.

There are two progress notification modes for the progress notifications:

  • reportIndefinitely - the registration will stay active until the callback is unregistered and will always report the most up-to-date number of transferable bytes. This type of callback could be used to control a network indicator UI that, for example, changes color or appears only when uploads or downloads are actively taking place.

let realm = new Realm(config);
const progressCallback = (transferred, transferables) => {
if (transferred < transferables) {
// Show progress indicator
} else {
// Hide the progress indicator
}
};
realm.syncSession.addProgressNotification('upload', 'reportIndefinitely', progressCallback);
// ...
realm.syncSession.removeProgressNotification(progressCallback);
  • forCurrentlyOutstandingWork - the registration will capture the number of transferable bytes at the moment it is registered and always report progress relative to that value. Once the number of transferred bytes reaches or exceeds that initial value, the callback will be automatically unregistered. This type of progress notification could, for example, be used to control a progress bar that tracks the progress of an initial download of a synced Realm when a user signs in, letting them know how long it is before their local copy is up-to-date.

let realm = new Realm(config);
const progressCallback = (transferred, transferable) => {
const progressPercentage = transferred / transferable;
};
realm.syncSession.addProgressNotification('download', 'forCurrentlyOutstandingWork', progressCallback);
// ...
realm.syncSession.removeProgressNotification(progressCallback);

The asynchronous methods Realm.open and Realm.openAsync for opening a Realm can also be used to register a callback for sync progress notifications. In this case only ‘download’ direction and ‘forCurrentlyOutstandingWork’ mode are supported. Additional callbacks can be registered after the initial open is completed by using the newly created Realm instance.

Realm.open(config)
.progress((transferred, transferable) => {
// update UI
})
.then(realm => {
// use the Realm
})
.catch((e) => reject(e));
Realm.openAsync(config,
(error, realm) => {
// use the Realm or report an error
},
(transferred, transferable) => {
// update UI
}, );

Session objects allow your app to monitor the status of a session’s uploads to and downloads from the Realm Object Server by registering a subscriber on the IObservable instance, obtained by calling session.GetProgressObservable(direction, mode).

The subscription callback will be invoked periodically by the synchronization subsystem. As many subscribers as needed can be registered on a session object simultaneously. Subscribers can either be configured to report upload progress or download progress. Each time a subscriber is called, it will be passed a SyncProgress instance that will contain information about the current number of bytes already transferred, as well as the total number of transferable bytes (defined as the number of bytes already transferred plus the number of bytes pending transfer).

Each time a subscriber is registered, an IDisposable token will be returned. You must keep a reference to that token for as long as progress notifications are desired. To stop receiving notifications, call Dispose on the token.

There are two modes for the progress subscriptions:

  • ReportIndefinitely - the subscription will stay active until Dispose is explicitly called and will always report the most up-to-date number of transferrable bytes. This type of callback could be used to control a network indicator UI that, for example, changes color or appears only when uploads or downloads are actively taking place.

var token = session.GetProgressObservable(ProgressDirection.Download, ProgressMode.ReportIndefinitely)
.Subscribe(progress =>
{
if (progress.TransferredBytes < progress.TransferableBytes)
{
// Show progress indicator
}
else
{
// Hide the progress indicator
}
});
  • ForCurrentlyOutstandingWork - the subscription will capture the number of transferable bytes at the moment it is registered and always report progress relative to that value. Once the number of transferred bytes reaches or exceeds that initial value, the subscriber will automatically unregister itself. This type of subscription could, for example, be used to control a progress bar that tracks the progress of an initial download of a synced Realm when a user signs in, letting them know how long it is before their local copy is up-to-date.

var token = session.GetProgressObservable(ProgressDirection.Download, ProgressMode.ForCurrentlyOutstandingWork)
.Subscribe(progress =>
{
var progressPercentage = progress.TransferredBytes / progress.TransferableBytes;
progressBar.SetValue(progressPercentage);
if (progressPercentage == 1)
{
progressBar.Hide();
}
});

Note that those examples use the ObservableExtensions.Subscribe extension method to simplify subscribing. We recommend using the Reactive Extensions class library as it greatly simplifies working with observable sequences. For example, here’s a more advanced scenario, where both upload and download progress is tracked, throttled, and finally, dispatched on the main thread:

var uploadProgress = session.GetProgressObservable(ProgressDirection.Upload, ProgressMode.ReportIndefinitely);
var downloadProgress = session.GetProgressObservable(ProgressDirection.Download, ProgressMode.ReportIndefinitely);
var token = uploadProgress.CombineLatest(downloadProgress, (upload, download) =>
{
return new
{
TotalTransferred = upload.TransferredBytes + download.TransferredBytes,
TotalTransferable = upload.TransferableBytes + download.TransferableBytes
};
})
.Throttle(TimeSpan.FromSeconds(0.1))
.ObserveOn(SynchronizationContext.Current)
.Subscribe(progress =>
{
if (progress.TotalTransferred < progress.TotalTransferable)
{
// Show spinner
}
else
{
// Hide spinner
}
});

Schema Changes

This section is very important to understand when working with Realm especially in a development environment where the details of schema are still being worked out. In a production environment, we recommend minimizing the number of schema changes by removing the canModifySchema permission so that users cannot change the schema design. More details can be found here.

When your Realm is synced with Realm Object Server, the migration process is a little different—and in many cases, simpler. Here’s what you need to know:

  1. Additive changes, such as adding a class or adding a field to a class, are applied automatically.

  2. Schema changes are backwards compatible - old clients will continue to sync with newer ones

  3. Removing a field from a schema doesn’t delete the field from the database, but instead instructs Realm to ignore that field. New objects will continue to be created with those properties, but they will be set to null. Non-nullable fields will be set to appropriate zero/empty values: 0 for numeric fields, an empty string for string properties, and so on.

  4. You don’t need to set the schema version (though you are welcome to)

  5. You must not include a migration block.

Additive Changes

Synchronized Realms only support additive changes. This is to ensure that older clients can continue to sync with newer clients. As a result, synced migrations tend to be simpler.

Swift
Objective-C
Java
Javascript
.Net

Suppose your application has the Dog class:

class Dog: Object {
@objc dynamic var name = ""
}

Now you need to add the Person class and give it an owner relationship to Dog. You don’t need to do anything other than adding the class and associated properties before syncing:

class Dog: Object {
@objc dynamic var name = ""
@objc dynamic var owner: Person?
}
class Person: Object {
@objc dynamic var name = ""
@objc dynamic var birthdate: NSDate? = nil
}
let syncServerURL = URL(string: "http://localhost:9080/Dogs")!
let config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: user, realmURL: syncServerURL))
let realm = try! Realm(configuration: config)

Suppose your application has the Dog class:

@interface Dog : RLMObject
@property NSString *name;
@end

Now you need to add the Person class and give it an owner relationship to Dog. You don’t need to do anything other than adding the class and associated properties before syncing:

@interface Dog : RLMObject
@property NSString *name;
@property Person *owner;
@end
RLM_ARRAY_TYPE(Dog)
@interface Person : RLMObject
@property NSString *name;
@property NSDate *birthdate;
@end
RLM_ARRAY_TYPE(Person)
// Required properties of an Objective‑C reference
// type have to be declared in combination with:
@implementation Person
+ (NSArray *)requiredProperties {
return @[@"name"];
}
@end
NSURL *syncServerURL = [NSURL URLWithString:@"http://localhost:9080/Dogs"];
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:user realmURL:syncServerURL];
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];

Suppose your application has the Dog class:

public class Dog extends RealmObject {
private String name = "";
}

Now you need to add the Person class and give it an owner relationship to Dog. You don’t need to do anything other than adding the class and associated properties before syncing:

public class Dog extends RealmObject {
private String name = "";
private Person owner;
// getter / setter
}
public class Person extends RealmObject {
private String name = "";
private Date birthdate;
// getter / setter
}
String syncServerURL = "realm://localhost:9080/Dogs"
SyncConfiguration config = new SyncConfiguration.Builder(user, syncServerURL).build();
Realm realm = Realm.getInstance(config);

Suppose your application has the Dog class:

let schemas.Dog = {
name: 'Dog',
properties: {
name: 'string'
}
};

Now you need to add the Person class and give it an owner relationship to Dog. You don’t need to do anything other than adding the class and associated properties before syncing:

let schemas.Dog = {
name: 'Dog',
properties: {
name: 'string',
owner: 'Person'
}
};
let schemas.Person = {
name: 'Person',
properties: {
name: 'string',
birthdate: 'date'
}
};
let config = {
sync: {
user: user,
url: syncServerUrl
},
schema: [schemas.Person, schemas.Dog]
};
Realm.open(config).then(realm => { /* ... */ });

Suppose your application has the Dog class:

public class Dog : RealmObject
{
public string Name { get; set; }
}

Now you need to add the Person class and give it an Owner relationship to Dog. You don’t need to do anything other than adding the class and associated properties before syncing:

public class Dog : RealmObject
{
public string Name { get; set; }
public Person Owner { get; set; }
}
public class Person : RealmObject {
public string Name { get; set; }
public DateTimeOffset Birthdate { get; set; }
}
var syncServerUri = new Uri("http://localhost:9080/Dogs");
var config = new SyncConfiguration(user, syncServerUri);
var realm = Realm.GetInstance(config);

Destructive Changes

If you need to perform a destructive schema change, you will need to create a new Realm. You can also manually delete the existing Realm and recreate it if you are in development.

Since synced Realms don’t support migration blocks, destructive changes for a migration need to be handled in a different way. Create a new synchronized Realm with the new schema, and copy data from the old Realm to the new Realm:

The following are considered destructive changes:

  • Changing a property’s type but keeping the same name

  • Changing a primary key

  • Changing a property from optional to required (or vice-versa)

Swift
Objective-C
Java
Javascript
.Net
class Dog: Object {
@objc dynamic var name = ""
@objc dynamic var owner: Person?
}
class Person: Object {
@objc dynamic var name = ""
}
class PersonV2: Object {
@objc dynamic var name: String? = nil
}
var syncServerURL = URL(string: "realm://localhost:9080/Dogs")!
var config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: user, realmURL: syncServerURL))
// Limit to initial object type
config.objectTypes: [Dog.self, Person.self]
let initialRealm = try! Realm(configuration: config)
syncServerURL = URL(string: "realm://localhost:9080/DogsV2")!
config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: user, realmURL: syncServerURL))
// Limit to new object type
config.objectTypes: [Dog.self, PersonV2.self]
let newRealm = try! Realm(configuration: config)

Custom migrations can also be applied to synced Realms by writing a notification handler on the client side to make the changes, or as a server-side event handler. However, if the migration makes a destructive change, the Realm will stop syncing with ROS, producing a Bad changeset received error.

@interface Dog : RLMObject
@property NSString *name;
@property Person *owner;
@end
RLM_ARRAY_TYPE(Dog)
@interface Person : RLMObject
@property NSString *name;
@end
RLM_ARRAY_TYPE(Person)
@implementation Person
+ (NSArray *)requiredProperties {
return @[@"name"];
}
@interface PersonV2 : RLMObject
@property NSString *name;
@end
RLM_ARRAY_TYPE(PersonV2)
@implementation PersonV2
+ (NSArray *)requiredProperties {
return @[];
}
NSURL *syncServerURL = [NSURL URLWithString: @"realm://localhost:9080/Dogs"];
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:user realmURL:syncServerURL];
// Limit to initial object type
config.objectClasses = @[Dog.class, Person.class];
RLMRealm *initialRealm = [RLMRealm realmWithConfiguration:config error:nil];
syncServerURL = [NSURL URLWithString: @"realm://localhost:9080/DogsV2"];
config = [RLMRealmConfiguration defaultConfiguration];
config.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:user realmURL:syncServerURL];
// Limit to new object types
config.objectClasses = @[Dog.class, PersonV2.class];
RLMRealm *newRealm = [RLMRealm realmWithConfiguration:config error:nil];

Custom migrations can also be applied to synced Realms by writing a notification handler on the client side to make the changes, or as a server-side event handler. However, if the migration makes a destructive change, the Realm will stop syncing with ROS, producing a Bad changeset received error.

public class Dog extends RealmObject {
private String name = "";
private Person owner;
// getter / setter
}
public class Person extends RealmObject {
@Required
private String name = "";
// getter / setter
}
public class PersonV2 extends RealmObject {
//no longer required
private String name = "";
// getter / setter
}
@RealmModule(classes = { Person.class, Dog.class }) class InitialModule {}
String syncServerURL = "realm://localhost:9080/Dogs"
SyncConfiguration config = new SyncConfiguration.Builder(user, syncServerURL)
.modules(new InitialModule())
.build();
// Limit to initial object type
Realm initialRealm = Realm.getInstance(config);
@RealmModule(classes = { PersonV2.class, Dog.class }) class NewModule {}
String syncServerURL = "realm://localhost:9080/Dogs"
SyncConfiguration config = new SyncConfiguration.Builder(user, syncServerURL)
.modules(new NewModule())
.build();
// Limit to new object type
Realm initialRealm = Realm.getInstance(config);

Your application can do this by listening for notifications on the old Realm, making the changes and copying them to the new Realm. (This is a good use case for Dynamic Realms.) You can also use a server-side event handler.

const DogSchema = {
name: 'Dog',
properties: {
name: {type: 'string', default: ''},
owner: 'Person'
}
};
const PersonSchema = {
name: 'Person',
properties: {
name: {type: 'string', default: ''}
}
};
const PersonSchemaV2 = {
name: 'Person',
//changing name to an optionaal property
properties: {
name: {type: 'string', optional: true}
}
};
let config = {
sync: {
user: user,
url: 'realm://localhost:9080/Dogs'
},
schema: [DogSchema, PersonSchema]
}
let realm = new Realm(config);
let configV2 = {
sync: {
user: user,
url: 'realm://localhost:9080/DogsV2'
},
schema: [DogSchema, PersonSchemaV2]
}
let realmV2 = new Realm(configV2);
// TODO copy objects from realm to realmV2
public class Dog : RealmObject
{
public string Name { get; set; }
public Person Owner { get; set; }
}
// We can't change the name of the class in Realm, so we're
// using [MapTo] to avoid collisions between the .NET classes.
[MapTo("Person")]
public class Person_Old : RealmObject
{
// By default strings are nullable
public string Name { get; set; }
}
public class Person : RealmObject
{
// Changing optionality is destructive change
[Required]
public string Name { get; set; }
}
var initialConfig = new SyncConfiguration(user, new Uri("http://localhost:9080/Dogs"))
{
// Limit to initial object type
ObjectClasses = new[] { typeof(Dog), typeof(Person_Old) }
};
var initialRealm = Realm.GetInstance(initialConfig);
var newConfig = new SyncConfiguration(user, new Uri("http://localhost:9080/DogsV2"))
{
// Limit to new object type
ObjectClasses = new[] { typeof(Dog), typeof(Person) }
};
var newRealm = Realm.GetInstance(newConfig);

Custom migrations can also be applied to synced Realms by writing a notification handler on the client side to make the changes, or as a server-side event handler. However, if the migration makes a destructive change, the Realm will stop syncing with ROS, producing a Bad changeset received error.

Best Practices

Writing Data

When building an app with Realm, care must be taken when deciding when to use a write transactions and which data should be modified per transaction. This is because Realm sync works atomically -- it downloads or uploads data for a single transaction and then commits that transaction to disk before downloading the next transaction. This can become problematic if you bulk-load a lot of data into a single transaction because old mobile devices may not have the available memory or compute to hold the entire transaction in-memory before committing to disk. Additionally, acquiring and releasing the write lock for a transaction does consume resources so you should look to find a middle ground between a full bulk load and a transaction per object. The right number of objects per transaction will depend on your app and schema and design but a good rule of thumb to follow is 10,000 objects or 1MB per transaction.

Reactive App Architectures

One of the great advantages of Realm is that any object which is presented in the UI to the user always represents the correct state of that object - an object never needs to be refreshed or re-fetched, a concept that we call “live objects.” However, in many Android or iOS architectures, especially in Reactive or LiveData designs, it is common to detach or abstract away the data layer from the UI. Realm supports these common practices, and a Realm object may be detached from the data layer and loaded as an unmanaged object to the view but it will not always represent the current state of that object since it has been loaded into memory. Care must be taken when writing an unmanaged object back into Realm because by default the write of the realm object will be interpreted as a completely new object to Realm sync. In order to avoid this we alway recommended using the .copyToRealmOrUpdate flag which will perform an intelligent diff on the object before insertion and only update the fields if necessary.

Syncing Large Objects (Photos, Videos, etc)

While the Realm database can be used to store binary data we generally do not recommend storing blob data in Realm because it can be inefficient for Realm sync. The diffing algorithm for syncing changes works on a field level, so if a single piece of the image changes, the entire image will need to be re-synced instead of just the individual bits that changed. This same logic can be applied for large JSON blobs as well which are stored as large string fields -- if a single key-value changes, the entire JSON must be re-synced. Instead, if you wanted to use Realm sync for transferring images, we recommend using Realm sync to transfer the image to ROS and then have the Event Handler react to the new image, remove the image from Realm and store it in some object storage such as S3, and then simply store a reference to the URL in the Realm database. If the client wants to see the full image again they can pull the image from the URL via REST or file access.

Working with Threads

One important thing to consider when using Realm is that a realm is thread-confined; references to objects cannot be passed across threads. Because of this, there are several conveniences APIs in most bindings that help with performing writes on background threads such as the executeTransactionAsync on Android. If no such convenience API is available to perform your work we recommend dispatching the work to a background thread but attaching a notification token to the work to know when the background job is done, then opening a new realm reference on the background thread, performing the work such as large write transaction or a long query, and then returning the result to main or UI thread. Be sure to close the realm or null out the realm reference opened on the background thread so that it can be garbage collected. A common cause for client side realms ballooning in size is forgetting to clean up all those realm references made on background threads. Be sure to implement the compactOnLaunch option when opening realms to help mitigate this problem but be careful because this will block the app if the realm size is massive.

Not what you were looking for? Leave Feedback