Docs Menu

Configure & Open a Synced Realm - Swift SDK

On this page

You can configure a realm to automatically synchronize data between many devices that each have their own local copy of the data. Synced realms use a different configuration than local-only realms and require an Atlas App Services backend to handle the synchronization process.

Applications can always create, modify, and delete synced realm objects locally, even when offline. Whenever a network connection is available, the Realm SDK opens a connection to an application server and syncs changes to and from other clients. The Atlas Device Sync protocol and server-side operational transforms guarantee that all fully synced instances of a realm see exactly the same data, even if some changes occurred offline and/or were received out of order.

Tip
Learn How to Configure and Use Sync

For more information on Device Sync, including directions on how to set up sync in an App Services app, see Sync Data.

Synced realms differ from non-synced local Realm Database in a few ways:

  • Synced realms attempt to sync changes with your backend Atlas App Services App, whereas non-synced realms do not.
  • Synced realms can be accessed by authenticated users, while non-synced realms have no concept of users or authentication.
  • With synced realms, you can specify the download behavior to download updates before opening a realm. However, requiring changes to download before opening the realm requires the user to be online. Non-synced realms can always be used offline.

You can copy data from a non-synced Realm Database to a synced realm, and vice versa, but you cannot sync a non-synced Realm Database.

Realm does not have a direct mechanism to add sync to a non-synced realm, or to permanently stop Sync for a synced realm. However, the Swift SDK does provide methods that enable you to copy a realm file for use with a different configuration. With these methods, you can easily duplicate a realm's data, which you can then open with a sync or non-sync configuration. This lets you indirectly add Sync to a non-synced realm, or permanently stop a realm from syncing. See:

Tip
See also:

The examples on this page describe how to work with synced realms in Swift SDK versions newer than 10.15.0. For older SDK versions, or if you're building apps for iOS targets prior to 15.0, with Swift versions older than 5.5, see: Legacy Sync Open Methods.

The typical flow for opening a synced realm involves:

  1. Authenticating the user.
  2. Creating a sync configuration.
  3. Opening the user's synced realm with the configuration.

At authentication, we cache user credentials in a sync_metadata.realm file on device.

When you open a synced realm after authenticating, you can bypass the login flow and go directly to opening the synced realm, using the same sync configuration you already created.

With cached credentials, you can:

  • Open a synced realm immediately with the data that is on the device. You can use this method offline or online.
  • Open a synced realm after downloading changes from your Atlas App Services App. This requires the user to have an active internet connection.

Initialize a synced realm with a sync configuration. This enables you to specify a partition value whose data should sync to the realm.

New in version 10.22.0.

When you use Flexible Sync, use the flexibleSyncConfiguration() to open a synced realm.

let app = App(id: APPID)
let user = try await app.login(credentials: Credentials.anonymous)
var config = user.flexibleSyncConfiguration()
// Pass object types to the Flexible Sync configuration
// as a temporary workaround for not being able to add complete schema
// for a Flexible Sync app
config.objectTypes = [Task.self, Team.self]
let realm = try await Realm(configuration: config, downloadBeforeOpen: .always)
Important
Flexible Sync Requires a Subscription

You can't use a Flexible Sync realm until you add at least one subscription. To learn how to add subscriptions, see: Add a Subscription.

New in version 10.23.0.

If you want to open a synced realm as a different Sync user, you can use the writeCopy(configuration: ) method to make a copy of the synced realm for use with the new user's sync configuration. The example below creates a copy of the synced realm, with all of its existing data, that you can use with a different sync configuration.

After you copy the realm for the new Sync user's configuration, you can open the copy as a synced realm for that user.

Note
Partition-Based Sync Only

This method only supports Partition-Based Sync. If your app uses Flexible Sync, you must manually iterate through the objects in one realm and copy them into the other realm.

func testConvertSyncToSync() async throws {
let app = App(id: YOUR_APP_SERVICES_APP_ID)
// Log in the user whose realm you want to use with another sync user
let frodoBaggins = try await app.login(credentials: Credentials.anonymous)
var frodoConfig = frodoBaggins.configuration(partitionValue: "Some Partition Value")
frodoConfig.objectTypes = [QsTask.self]
// Open the synced realm, and confirm it contains the data we want
// the other user to be able to access.
let frodoRealm = try await Realm(configuration: frodoConfig, downloadBeforeOpen: .always)
let frodoRealmTasks = frodoRealm.objects(QsTask.self)
let frodoSyncedTasks = frodoRealmTasks.where { $0.owner == "Frodo" }
XCTAssertEqual(frodoSyncedTasks.count, 3)
print("Successfully opened frodo's realm and it contains this many tasks: \(frodoSyncedTasks.count)")
// Log in as the user who will work with frodo's synced realm
let samwiseGamgee = try await app.login(credentials: Credentials.anonymous)
var samConfig = samwiseGamgee.configuration(partitionValue: "Some Partition Value")
samConfig.objectTypes = [QsTask.self]
// Specify an output directory for the copied realm
// We're using FileManager here for tested code examples.
guard let outputDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
// Append a file name to complete the path
let copiedRealmFilePath = outputDir.appendingPathComponent("copied.realm")
// Update the config file path to the path where you want to save the copied realm
samConfig.fileURL = copiedRealmFilePath
// Make a copy of frodo's realm that uses sam's config
try frodoRealm.writeCopy(configuration: samConfig)
// Open sam's realm, and see that it contains the same data as frodo's realm
let samRealm = try await Realm(configuration: samConfig)
let samRealmTasks = samRealm.objects(QsTask.self)
var samSyncedTasks = samRealmTasks.where { $0.owner == "Frodo" }
print("Successfully opened sam's realm and it contains this many tasks: \(samSyncedTasks.count)")
XCTAssertEqual(frodoSyncedTasks.count, samSyncedTasks.count)
// Add a task to sam's realm
let task = QsTask(value: ["name": "Keep an eye on that Gollum", "owner": "Sam"])
try! samRealm.write {
samRealm.add(task)
}
// See that the new task reflects in sam's realm, but not frodo's
samSyncedTasks = samRealmTasks.where { $0.owner == "Sam" }
XCTAssertEqual(samSyncedTasks.count, 1)
let samTasksInFrodoRealm = frodoRealmTasks.where { $0.owner == "Sam" }
XCTAssertEqual(samTasksInFrodoRealm.count, 0)
}

New in version 10.23.0.

If you want a non-synced realm to start syncing with other devices and your Atlas App Services backend, you can use the writeCopy(configuration: ) method to make a copy of the non-synced realm for use with a sync configuration. The example below creates a copy of a non-synced realm file, with all of its existing data, that you can use with a sync configuration.

After you copy the realm for use with Sync, you can open the copy as a synced realm. Any changes you make to the synced realm will reflect in the synced realm file, and they will also propogate to other devices and the Atlas App Services backend.

Note
Partition-Based Sync Only

This method only supports Partition-Based Sync. If your app uses Flexible Sync, you must manually iterate through the objects in one realm and copy them into the other realm.

func testConvertLocalToSync() async throws {
let app = App(id: YOUR_APP_SERVICES_APP_ID)
// Log in the user whose realm you want to open as a synced realm
let syncUser = try await app.login(credentials: Credentials.anonymous)
// Create a configuration to open the sync user's realm
var syncConfig = syncUser.configuration(partitionValue: "Your Partition Value")
syncConfig.objectTypes = [QsTask.self]
// Prepare the configuration for the user whose local realm you
// want to convert to a synced realm
var localConfig = Realm.Configuration()
localConfig.objectTypes = [QsTask.self]
// For this example, add some data to the local realm
// before copying it. No need to do this if you're
// copying a realm that already contains data.
let localRealm = addExampleData(config: localConfig)
// Create a copy of the local realm that uses the
// sync configuration. All the data that is in the
// local realm is available in the synced realm.
try! localRealm.writeCopy(configuration: syncConfig)
// Open the synced realm we just created from the local realm
let syncedRealm = try await Realm(configuration: syncConfig)
// Access the Task objects in the synced realm to see
// that we have all the data we expect
let syncedTasks = syncedRealm.objects(QsTask.self)
var frodoSyncedTasks = syncedTasks.where { $0.owner == "Frodo" }
XCTAssertEqual(frodoSyncedTasks.count, 3)
print("Synced realm opens and contains this many tasks: \(frodoSyncedTasks.count)")
// Add a new task to the synced realm, and see it in the task count
let task4 = QsTask(value: ["name": "Send gift basket to Tom Bombadil", "owner": "Frodo"])
try! syncedRealm.write {
syncedRealm.add(task4)
}
frodoSyncedTasks = syncedTasks.where { $0.owner == "Frodo" }
XCTAssertEqual(frodoSyncedTasks.count, 4)
print("After adding a task, the synced realm contains this many tasks: \(frodoSyncedTasks.count)")
// Open the local realm, and confirm that it still only contains 3 tasks
let openedLocalRealm = try await Realm(configuration: localConfig)
let localTasks = openedLocalRealm.objects(QsTask.self)
let frodoLocalTasks = localTasks.where { $0.owner == "Frodo" }
XCTAssertEqual(frodoLocalTasks.count, 3)
print("Local realm opens and contains this many tasks: \(frodoLocalTasks.count)")
XCTAssertNotEqual(frodoLocalTasks.count, frodoSyncedTasks.count)
/// Populate the local realm with some data that we'll use in the synced realm.
func addExampleData(config: Realm.Configuration) -> Realm {
// Prepare the configuration for the user whose local realm you
// want to convert to a synced realm
let localConfig = config
// Open the local realm, and populate it with some data before returning it
let localRealm = try! Realm(configuration: localConfig)
let task1 = QsTask(value: ["name": "Keep it secret", "owner": "Frodo"])
let task2 = QsTask(value: ["name": "Keep it safe", "owner": "Frodo"])
let task3 = QsTask(value: ["name": "Journey to Bree", "owner": "Frodo"])
try! localRealm.write {
localRealm.add([task1, task2, task3])
}
return localRealm
}
}

New in version 10.23.0.

Tip

You may temporarily pause a Sync session if you do not want to permanently change a synced realm to a non-synced realm. See: Suspend or Resume a Sync Session.

If you want to permanently stop a realm from syncing to your Atlas App Services backend, you can use the writeCopy(configuration: ) method to make a copy of a synced realm for use with a non-sync configuration. The example below creates a copy of the realm file, with all of its existing data, at a file URL you specify.

This process removes the realm_id in the local realm. You must increment the schema version as if you had deleted a property.

After you copy the realm for use without Sync, you can open the copy as a non-synced realm. Any changes you make to the non-synced realm reflect only in the local realm file. No changes propogate to other devices or the Atlas App Services backend.

func testConvertSyncToLocal() async throws {
let app = App(id: YOUR_APP_SERVICES_APP_ID)
// Log in the user whose realm you want to open as a local realm
let syncUser = try await app.login(credentials: Credentials.anonymous)
// Create a configuration to open the seed user's realm
var syncConfig = syncUser.configuration(partitionValue: "Some Partition Value")
syncConfig.objectTypes = [QsTask.self]
// Open the realm with the Sync user's config, downloading
// any remote changes before opening.
let syncedRealm = try await Realm(configuration: syncConfig, downloadBeforeOpen: .always)
print("Successfully opened realm: \(syncedRealm)")
// Verify the data we expect in the realm
// The synced realm we are copying contains 3 tasks whose owner is "Frodo"
let syncedTasks = syncedRealm.objects(QsTask.self)
var frodoSyncedTasks = syncedTasks.where { $0.owner == "Frodo" }
XCTAssertEqual(frodoSyncedTasks.count, 3)
print("Synced realm opens and contains this many tasks: \(frodoSyncedTasks.count)")
// Construct an output file path for the local Realm
guard let outputDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
// Append a file name to complete the path
let localRealmFilePath = outputDir.appendingPathComponent("local.realm")
// Construct a local realm configuration
var localConfig = Realm.Configuration()
localConfig.objectTypes = [QsTask.self]
localConfig.fileURL = localRealmFilePath
// `realm_id` will be removed in the local realm, so we need to bump
// the schema version.
localConfig.schemaVersion = 1
// Check to see if there is already a realm at the local realm file path. If there
// is already a realm there, delete it.
if Realm.fileExists(for: localConfig) {
try Realm.deleteFiles(for: localConfig)
print("Successfully deleted existing realm at path: \(localRealmFilePath)")
} else {
print("No file currently exists at path")
}
// Make a copy of the synced realm that uses a local configuration
try syncedRealm.writeCopy(configuration: localConfig)
// Try opening the realm as a local realm
let localRealm = try await Realm(configuration: localConfig)
// Verify that the copied realm contains the data we expect
let localTasks = localRealm.objects(QsTask.self)
var frodoLocalTasks = localTasks.where { $0.owner == "Frodo" }
XCTAssertEqual(frodoLocalTasks.count, 3)
print("Local realm opens and contains this many tasks: \(frodoLocalTasks.count)")
let task = QsTask(value: ["name": "Send gift basket to Tom Bombadil", "owner": "Frodo"])
try! localRealm.write {
localRealm.add(task)
}
frodoLocalTasks = localTasks.where { $0.owner == "Frodo" }
XCTAssertEqual(frodoLocalTasks.count, 4)
print("After adding a task, the local realm contains this many tasks: \(frodoLocalTasks.count)")
frodoSyncedTasks = syncedTasks.where { $0.owner == "Frodo" }
XCTAssertEqual(frodoSyncedTasks.count, 3)
print("After writing to local realm, synced realm contains this many tasks: \(frodoSyncedTasks.count)")
XCTAssertNotEqual(frodoLocalTasks.count, frodoSyncedTasks.count)
}

New in version 10.15.0.

When you open a synced realm with the Swift SDK, you can pass the downloadBeforeOpen parameter to specify whether to download the changeset from your Atlas App Services App before opening the realm. This parameter accepts a case from the OpenBehavior enum:

  • never: Immediately open the realm on the device. Download changes in the background when the user has internet, but don't block opening the realm.
  • always: Check for changes every time you open the realm. Requires the user to have an active internet connection.
  • once: Download data before opening a realm for the first time, but open it without downloading changes on subsequent opens. This lets you populate a realm with initial data, but enables offline-first functionality on subsequent opens.
func testSpecifyDownloadBehavior() async throws {
let app = App(id: YOUR_REALM_APP_ID)
let user = try await app.login(credentials: Credentials.anonymous)
let partitionValue = "some partition value"
var configuration = user.configuration(partitionValue: partitionValue)
let realm = try await Realm(configuration: configuration, downloadBeforeOpen: .always)
print("Successfully opened realm after downloading: \(realm)")
}

When your Realm application authenticates a user, it caches the user's credentials. You can check for existing user credentials to bypass the login flow and access the cached user. Use this to open a realm offline.

Note
Initial login requires a network connection

When a user signs up for your app, or logs in for the first time with an existing account on a client, the client must have a network connection. Checking for cached user credentials lets you open a realm offline, but only if the user has previously logged in while online.

You can only open a synced realm offline if you do not require your client app to always download changes before opening the realm.

// Log the user into the backend app.
// The first time you login, the user must have a network connection.
func getUser() async throws -> User {
// Check for an existing user.
// If the user is offline but credentials are
// cached, this returns the existing user.
if let user = app.currentUser {
return user
} else {
// If the device has no cached user
// credentials, log them in.
let app = App(id: YOUR_APP_SERVICES_APP_ID)
let loggedInUser = try await app.login(credentials: Credentials.anonymous)
return loggedInUser
}
}
let user = try await getUser()
var configuration = user.configuration(partitionValue: "Some Partition Value")
// Open a Realm with this configuration.
// If you do not require the app to download updates
// before opening the realm, the realm just opens, even if
// offline.
let realm = try await Realm(configuration: configuration)
print("Successfully opened realm: \(realm)")
←  Add Device Sync to an App - Swift SDKManage Flexible Sync Subscriptions - Swift SDK →
Give Feedback
© 2022 MongoDB, Inc.

About

  • Careers
  • Investor Relations
  • Legal Notices
  • Privacy Notices
  • Security Information
  • Trust Center
© 2022 MongoDB, Inc.