Docs Menu

Flexible Sync - Swift SDK

On this page

  • Overview
  • About the Examples on This Page
  • Subscribe to Queryable Fields
  • Add a Subscription
  • Wait for Subscription Changes to Sync
  • Update Subscriptions with a New Query
  • Remove Subscriptions

New in version 10.22.0.

Flexible Sync uses subscriptions and permissions to determine which data to sync with your Realm app.

To use Flexible Sync in an iOS client:

You can add, update, and remove query subscriptions to determine which data syncs to the client device.

Important

Flexible Sync is currently in preview. We encourage you to try out the feature and give feedback, but please be aware that:

  • Configuration details may change.
  • SDK API implementation details may change.

We do not recommend using Flexible Sync in production applications while it is still in preview.

Tip
See also:

This page details how to manage subscriptions for Flexible Sync.

For general information about using Realm Sync with the Swift SDK, such as how to sync changes in the background or pause a sync session, see: Sync Changes Between Devices.

For information about setting up permissions for Flexible Sync, see: Flexible Sync Rules & Permissions.

The examples on this page use a simple data set for a task list app. The two Realm object types are Team and Task. A Task has a taskName, assignee's name, and completed flag. There is also an arbitrary number for priority -- higher is more important -- and a count of minutes spent working on it. A Team has a teamName, zero or more Tasks, and a list of members.

class Task: Object {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var taskName: String
@Persisted var assignee: String?
@Persisted var completed: Bool
@Persisted var progressMinutes: Int
}
class Team: Object {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var teamName: String
@Persisted var tasks: List<Task>
@Persisted var members: List<String>
}

When you configure Flexible Sync on the backend, you specify which fields your client application can query. In the client application, use the subscriptions API to manage a set of subscriptions to specific queries on queryable fields.

You can:

  • Add subscriptions
  • React to subscription state
  • Update subscriptions with new queries
  • Remove individual subscriptions or all subscriptions for an object type

Data matching the subscription, where the user has the appropriate permissions, syncs between devices and the backend application.

You can specify an optional string name for your subscription.

When you create a subscription, Realm looks for data matching a query on a specific object type. You can have multiple subscription sets on different object types. You can also have multiple queries on the same object type.

Important
Object Links

You must add both an object and its linked object to the subscription set to see a linked object.

If your subscription results contain an object with a property that links to an object not contained in the results, the link appears to be nil. There is no way to distinguish whether that property's value is legitimately nil, or whether the object it links to exists but is out of view of the query subscription.

Example

You can create a subscription with an explicit name. Then, you can search for that subscription by name to update or remove it.

QuerySubscription<Task>(name: "long-running-completed") {
$0.completed == true && $0.progressMinutes > 120
}

If you do not specify a name for a subscription, you can search for the subscription by the query string.

QuerySubscription<Team> {
$0.teamName == "Developer Education"
}
Note
Duplicate subscriptions

Subscription names must be unique. Trying to append a subscription with the same name as an existing subscription throws an error.

If you do not explicitly name a subscription, and instead subscribe to the same unnamed query more than once, Realm does not persist duplicate queries to the subscription set.

If you subscribe to the same query more than once under different names, Realm persists both subscriptions to the subscription set.

Add a subscription in a subscriptions write block. You append each new subscription to the client's Realm subscriptions.

Important

Flexible Sync does not support all the operators available in Realm Query Language. See Flexible Sync RQL Limitations for details.

let subscriptions = realm.subscriptions
subscriptions.write({
subscriptions.append(
QuerySubscription<Team> {
$0.teamName == "Developer Education"
})
}, onComplete: { error in // error is optional
if error == nil {
// Flexible Sync has updated data to match the subscription
} else {
// Handle the error
}
})

You can add multiple subscriptions within a subscription write block, including subscriptions of different object types.

let subscriptions = realm.subscriptions
subscriptions.write({
subscriptions.append(
QuerySubscription<Task>(name: "completed-tasks") {
$0.completed == true
})
subscriptions.append(
QuerySubscription<Team> {
$0.teamName == "Developer Education"
})
}, onComplete: { error in // error is optional
if error == nil {
// Flexible Sync has updated data to match the subscription
} else {
// Handle the error
}
})

Writing an update to the subscription set locally is only one component of changing a subscription. After the local subscription change, the realm synchronizes with the server to resolve any updates to the data due to the subscription change. This could mean adding or removing data from the synced realm.

If your application does not use Swift's async/await feature, you can react to subscription changes syncing with the server using the onComplete block. This block is called after subscriptions are synchronized with the server. If you want to react to subscription state changes by redrawing a UI, for example, or taking another action based on changes to the data set, take those actions in onComplete. This is also where you can handle optional errors that occur during synchronization.

let subscriptions = realm.subscriptions
subscriptions.write({
subscriptions.append(
QuerySubscription<Task> {
$0.assignee == "John Doe"
})
}, onComplete: { error in // error is optional
if error == nil {
// Flexible Sync has updated data to match the subscription
} else {
// Handle the error
}
})

If your application uses async/await, you don't need the onComplete block. The write transaction executes asynchronously and throws an error if the transaction cannot complete successfully.

func changeSubscription() async throws {
let subscriptions = realm.subscriptions
try await subcriptions.write {
subscriptions.remove {
QuerySubscription<Task> {
$0.assignee == "Joe Doe"
}
}
}
}

Additionally, you can watch the state of the subscription set with the SyncSubscriptionState enum. You can use subscription state to:

  • Show a progress indicator while data is downloading
  • Find out when a subscription set becomes superseded
  • Wait for a subscription that has been persisted locally to sync with the server
  • Watch for errors

The superseded state is a SyncSubscriptionState that can occur when another thread writes a subscription on a different instance of the subscription set. If the state becomes superseded, you must obtain a new instance of the subscription set before you can write to it.

You can update subscriptions using update. In this example, we search for a subscription matching our query and then update that subscription with a new query.

let subscriptions = realm.subscriptions
let foundSubscription = subscriptions.first(ofType: Team.self, where: {
$0.teamName == "Developer Education"
})
subscriptions.write({
foundSubscription?.update(toType: Team.self, where: {
$0.teamName == "Documentation"
})
}, onComplete: { error in // error is optional
if error == nil {
// Flexible Sync has updated data to match the subscription
} else {
// Handle the error
}
})

You can also search for a subscription by name. In this example, we search for a subscription by name and then update that subscription with a new query.

let subscriptions = realm.subscriptions
let foundSubscription = subscriptions.first(named: "user-team")
subscriptions.write({
foundSubscription?.update(toType: Team.self, where: {
$0.teamName == "Documentation"
})
}, onComplete: { error in // error is optional
if error == nil {
// Flexible Sync has updated data to match the subscription
} else {
// Handle the error
}
})

To remove subscriptions, you can:

  • Remove a single subscription query
  • Remove all subscriptions to a specific object type
  • Remove all subscriptions

When you remove a subscription query, Realm asynchronously removes the synced data that matched the query from the client device.

You can remove a specific subscription query in a subscription write block using remove. Specify the query by name or use the query as a string name to find the appropriate query subscription to remove.

let subscriptions = realm.subscriptions
// Look for a specific subscription, and then remove it
let foundSubscription = subscriptions.first(named: "docs-team")
subscriptions.write({
subscriptions.remove(foundSubscription!)
}, onComplete: { error in // error is optional
if error == nil {
// Flexible Sync has updated data to match the subscription
} else {
// Handle the error
}
})
// Or remove a subscription that you know exists without querying for it
subscriptions.write {
subscriptions.remove(named: "existing-subscription")
}

If you want to remove all subscriptions to a specific object type, use the removeAll method with ofType in a subscription write block.

let subscriptions = realm.subscriptions
subscriptions.write({
subscriptions.removeAll(ofType: Team.self)
}, onComplete: { error in // error is optional
if error == nil {
// Flexible Sync has updated data to match the subscription
} else {
// Handle the error
}
})

To remove all subscriptions from the subscription set, use the removeAll method in a subscription write block.

Important

If you remove all subscriptions and do not add a new one, you'll get an error. A realm opened with a flexible sync configuration needs at least one subscription to sync with the server.

let subscriptions = realm.subscriptions
subscriptions.write({
subscriptions.removeAll()
}, onComplete: { error in // error is optional
if error == nil {
// Flexible Sync has updated data to match the subscription
} else {
// Handle the error
}
})
←  Sync Changes Between Devices - Swift SDKUse Realm Database with SwiftUI →
Give Feedback
© 2022 MongoDB, Inc.

About

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