Manage Flexible Sync Subscriptions - Java SDK
On this page
- Overview
- Subscribe to Queryable Fields
- Add a Subscription
- Wait for Subscription Changes to Sync
- Update Subscriptions with a New Query
- Remove Subscriptions
- Flexible Sync RQL Requirements and Limitations
- Indexed Queryable Fields Subscription Requirements
- Unsupported Query Operators in Flexible Sync
- List Queries
- Embedded or Linked Objects
Overview
Flexible Sync uses subscriptions and permissions to determine which data to sync with your App.
To use Flexible Sync in the SDK:
Authenticate a user in your client project.
You can add, update, and remove query subscriptions to determine which data syncs to the client device.
Tip
See also:
This page details how to manage subscriptions for Flexible Sync.
For general information about using Atlas Device Sync with the SDK, such as how to sync changes in the background or pause a sync session, check out Sync Changes Between Devices.
For information about setting up permissions for Flexible Sync, check out Flexible Sync Rules & Permissions.
Subscribe to Queryable Fields
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 construct queries with the Java SDK's fluent interface or Realm Query Language.
Important
Flexible Sync does not support all the operators available in Realm Query Language. See Flexible Sync RQL Limitations for details.
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 clients and the backend application.
You can specify an optional string name for your subscription.
Tip
Always Specify a Subscription Name
Always specify a subscription name if your application uses multiple subscriptions. This makes your subscriptions easier to look up, update, and delete elsewhere in your app.
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.
You can create a subscription with an explicit name. Then, you can search for that subscription by name to update or remove it.
SyncConfiguration config = new SyncConfiguration.Builder(app.currentUser()) .initialSubscriptions(new SyncConfiguration.InitialFlexibleSyncSubscriptions() { public void configure(Realm realm, MutableSubscriptionSet subscriptions) { // add a subscription with a name subscriptions.add(Subscription.create("frogSubscription", realm.where(Frog.class) .equalTo("species", "spring peeper"))); } }) .build(); Realm.getInstanceAsync(config, new Realm.Callback() { public void onSuccess(Realm realm) { Log.v("EXAMPLE", "Successfully opened a realm."); // later, you can look up this subscription by name Subscription subscription = realm.getSubscriptions().find("frogSubscription"); } });
val config = SyncConfiguration.Builder(app.currentUser()) .initialSubscriptions { realm, subscriptions -> // add a subscription with a name subscriptions.add( Subscription.create( "frogSubscription", realm.where(Frog::class.java) .equalTo("species", "spring peeper") ) ) } .build() Realm.getInstanceAsync(config, object : Realm.Callback() { override fun onSuccess(realm: Realm) { Log.v("EXAMPLE", "Successfully opened a realm.") // later, you can look up this subscription by name val subscription = realm.subscriptions.find("frogSubscription") } })
You can also search subscriptions by query. If you omit the name when creating a subscription, this is the only way to look up your subscription.
SyncConfiguration config = new SyncConfiguration.Builder(app.currentUser()) .initialSubscriptions(new SyncConfiguration.InitialFlexibleSyncSubscriptions() { public void configure(Realm realm, MutableSubscriptionSet subscriptions) { // add a subscription without assigning a name subscriptions.add(Subscription.create( realm.where(Frog.class) .equalTo("species", "spring peeper"))); } }) .build(); Realm.getInstanceAsync(config, new Realm.Callback() { public void onSuccess(Realm realm) { Log.v("EXAMPLE", "Successfully opened a realm."); // later, you can look up this subscription by query Subscription subscription = realm.getSubscriptions().find(realm.where(Frog.class) .equalTo("species", "spring peeper")); } });
val config = SyncConfiguration.Builder(app.currentUser()) .initialSubscriptions { realm, subscriptions -> // add a subscription without assigning a name subscriptions.add( Subscription.create( realm.where(Frog::class.java) .equalTo("species", "spring peeper") ) ) } .build() Realm.getInstanceAsync(config, object : Realm.Callback() { override fun onSuccess(realm: Realm) { Log.v("EXAMPLE", "Successfully opened a realm.") // later, you can look up this subscription by query val subscription = realm.subscriptions.find( realm.where( Frog::class.java ).equalTo("species", "spring peeper") ) } })
Note
Duplicate subscriptions
Subscription names must be unique. Adding 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
Add a subscription in a subscriptions write block. You append each new subscription to the client's Realm subscriptions.
SyncConfiguration config = new SyncConfiguration.Builder(app.currentUser()) .initialSubscriptions(new SyncConfiguration.InitialFlexibleSyncSubscriptions() { public void configure(Realm realm, MutableSubscriptionSet subscriptions) { subscriptions.add(Subscription.create("subscriptionName", realm.where(Frog.class) .equalTo("species", "spring peeper"))); } }) .build(); // instantiate a realm instance with the flexible sync configuration Realm.getInstanceAsync(config, new Realm.Callback() { public void onSuccess(Realm realm) { Log.v("EXAMPLE", "Successfully opened a realm."); } });
val config = SyncConfiguration.Builder(app.currentUser()) .initialSubscriptions { realm, subscriptions -> subscriptions.add( Subscription.create( "subscriptionName", realm.where(Frog::class.java) .equalTo("species", "spring peeper") ) ) } .build() // instantiate a realm instance with the flexible sync configuration Realm.getInstanceAsync(config, object : Realm.Callback() { override fun onSuccess(realm: Realm) { Log.v("EXAMPLE", "Successfully opened a realm.") } })
Note
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 null. There is no way to distinguish whether that property's value is legitimately null, or whether the object it links to exists but is out of view of the query subscription.
Wait for Subscription Changes to Sync
Writing an update to the subscription set locally is only one component of changing a subscription. After the local subscription change, the client 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. Use the waitForInitialRemoteData() builder method to force your application to block until client subscription data synchronizes to the backend before opening the realm:
SyncConfiguration config = new SyncConfiguration.Builder(app.currentUser()) .initialSubscriptions(new SyncConfiguration.InitialFlexibleSyncSubscriptions() { public void configure(Realm realm, MutableSubscriptionSet subscriptions) { subscriptions.add(Subscription.create("my subscription", realm.where(Frog.class) .equalTo("species", "poison dart"))); } }) .waitForInitialRemoteData(2112, TimeUnit.MILLISECONDS) .build(); Realm.getInstanceAsync(config, new Realm.Callback() { public void onSuccess(Realm realm) { Log.v("EXAMPLE", "Successfully opened a realm."); } });
val config = SyncConfiguration.Builder(app.currentUser()) .initialSubscriptions { realm, subscriptions -> subscriptions.add( Subscription.create( "my subscription", realm.where(Frog::class.java) .equalTo("species", "poison dart") ) ) } .waitForInitialRemoteData( 2112, TimeUnit.MILLISECONDS ) .build() Realm.getInstanceAsync(config, object : Realm.Callback() { override fun onSuccess(realm: Realm) { Log.v("EXAMPLE", "Successfully opened a realm.") } })
You can also use SubscriptionSet.waitForSynchronization() or SubscriptionSet.waitForSynchronizationAsync() to delay execution until subscription sync completes after instantiating a sync connection.
SubscriptionSet.State Enum
Additionally, you can watch the state of the subscription set with the SubscriptionSet.State enum. You can use subscription state to:
Show a progress indicator while data is downloading
Find out when a subscription set becomes superseded
You can access the state of your application's subscription set using SubscriptionSet.getState().
Note
Subscription State "Complete"
The subscription set state "complete" does not mean "sync is done" or "all documents have been synced". "Complete" means the following two things have happened:
The subscription has become the active subscription set that is currently being synchronized with the server.
The documents that matched the subscription at the time the subscription was sent to the server are now on the local device. Note that this does not necessarily include all documents that currently match the subscription.
The Realm SDK does not provide a way to check whether all documents that match a subscription have synced to the device.
Superseded
SUPERSEDED
is a SubscriptionSet.State
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.
Update Subscriptions with a New Query
You can update subscriptions using SubscriptionSet.update(). In this example, we use MutableSubscriptionSet.addOrUpdate() to update the query for the subscription named "my frog subscription":
realm.getSubscriptions().update(new SubscriptionSet.UpdateCallback() { public void update(MutableSubscriptionSet subscriptions) { // to update a named subscription, create a replacement with // the same name and add it to the subscription set subscriptions.addOrUpdate( Subscription.create("my frog subscription", realm.where(Frog.class) .equalTo("name", "Benedict Cumberburger"))); } });
realm.subscriptions.update { subscriptions -> // to update a named subscription, create a replacement with // the same name and add it to the subscription set subscriptions.addOrUpdate( Subscription.create( "my frog subscription", realm.where(Frog::class.java) .equalTo( "name", "Benedict Cumberburger" ) ) ) }
You cannot update subscriptions created without a name. However, you can look up unnamed subscriptions by their query, remove them from the subscription set, then add a new subscription with an updated query:
realm.getSubscriptions().update(new SubscriptionSet.UpdateCallback() { public void update(MutableSubscriptionSet subscriptions) { // to update an unnamed subscription, remove it from the // subscription set, then add your new query to the set Subscription mySubscription = subscriptions.find(realm.where(Frog.class) .equalTo("species", "cane toad")); subscriptions.remove(mySubscription); subscriptions.addOrUpdate( Subscription.create( realm.where(Frog.class) .equalTo("species", "albino cane toad"))); } });
realm.subscriptions.update { subscriptions -> // to update an unnamed subscription, remove it from the // subscription set, then add your new query to the set val mySubscription = subscriptions.find( realm.where( Frog::class.java ).equalTo( "species", "cane toad" ) ) subscriptions.remove(mySubscription) subscriptions.addOrUpdate( Subscription.create( realm.where(Frog::class.java) .equalTo( "species", "albino cane toad" ) ) ) }
Remove Subscriptions
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.
Remove a Single Subscription
You can remove a specific subscription query
using MutableSubscriptionSet.remove().
You can either look up the subscription by name, then pass the returned
subscription to remove()
, or pass the subscription name directly to
remove()
:
realm.getSubscriptions().update(new SubscriptionSet.UpdateCallback() { public void update(MutableSubscriptionSet subscriptions) { Subscription mySubscription = subscriptions.find("mySubscription"); subscriptions.remove(mySubscription); } });
realm.subscriptions.update { subscriptions -> val mySubscription = subscriptions.find("mySubscription") subscriptions.remove(mySubscription) }
Remove All Subscriptions to an Object Type
If you want to remove all subscriptions to a specific object type, pass a class to the removeAll() method:
realm.getSubscriptions().update(new SubscriptionSet.UpdateCallback() { public void update(MutableSubscriptionSet subscriptions) { subscriptions.removeAll(Frog.class); } });
realm.subscriptions.update { subscriptions -> subscriptions.removeAll( Frog::class.java ) }
Remove All Subscriptions
To remove all subscriptions from the subscription set, use removeAll() with no arguments:
Warning
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.
realm.getSubscriptions().update(new SubscriptionSet.UpdateCallback() { public void update(MutableSubscriptionSet subscriptions) { subscriptions.removeAll(); } });
realm.subscriptions.update { subscriptions -> subscriptions.removeAll() }
Flexible Sync RQL Requirements and Limitations
Indexed Queryable Fields Subscription Requirements
Adding an indexed queryable field to
your App can improve performance for simple queries on data that is strongly
partitioned. For example, an app where queries strongly map data to a device,
store, or user, such as user_id == $0, “641374b03725038381d2e1fb”
, is
a good candidate for an indexed queryable field. However, an indexed
queryable field has specific requirements for use in a query subscription:
The indexed queryable field must be used in every subscription query. It cannot be missing from the query.
The indexed queryable field must use an
==
orIN
comparison against a constant at least once in the subscription query. For example,user_id == $0, "641374b03725038381d2e1fb"
orstore_id IN $0, {1,2,3}
.
You can optionally include an AND
comparison as long as the indexed
queryable field is directly compared against a constant using ==
or IN
at least once. For example, store_id IN {1,2,3} AND region=="Northeast"
or store_id == 1 AND (active_promotions < 5 OR num_employees < 10)
.
Invalid Flexible Sync queries on an indexed queryable field include queries where:
The indexed queryable field does not use
AND
with the rest of the query. For examplestore_id IN {1,2,3} OR region=="Northeast"
is invalid because it usesOR
instead ofAND
. Similarly,store_id == 1 AND active_promotions < 5 OR num_employees < 10
is invalid because theAND
only applies to the term next to it, not the entire query.The indexed queryable field is not used in an equality operator. For example
store_id > 2 AND region=="Northeast"
is invalid because it uses only the>
operator with the indexed queryable field and does not have an equality comparison.The query is missing the indexed queryable field entirely. For example,
region=="Northeast
ortruepredicate
are invalid because they do not contain the indexed queryable field.
Unsupported Query Operators in Flexible Sync
Flexible Sync has some limitations when using RQL operators. When you write the query subscription that determines which data to sync, the server does not support these query operators. However, you can still use the full range of RQL features to query the synced data set in the client application.
Operator Type | Unsupported Operators |
---|---|
Aggregate Operators | @avg , @count , @max , @min , @sum |
Query Suffixes | DISTINCT , SORT , LIMIT |
Case insensitive queries ([c]
) cannot use indexes effectively.
As a result, case insensitive queries are not recommended, since they could lead to
performance problems.
Flexible Sync only supports @count
for array fields.
List Queries
Flexible Sync supports querying lists using the IN
operator.
You can query a list of constants to see if it contains the value of a queryable field:
// Query a constant list for a queryable field value "priority IN { 1, 2, 3 }"
If a queryable field has an array value, you can query to see if it contains a constant value:
// Query an array-valued queryable field for a constant value "'comedy' IN genres"
Warning
You cannot compare two lists with each other in a Flexible Sync query. Note that this is valid Realm Query Language syntax outside of Flexible Sync queries.
// Invalid Flexible Sync query. Do not do this! "{'comedy', 'horror', 'suspense'} IN genres" // Another invalid Flexible Sync query. Do not do this! "ANY {'comedy', 'horror', 'suspense'} != ANY genres"
Embedded or Linked Objects
Flexible Sync does not support querying on properties in Embedded Objects
or links. For example, obj1.field == "foo"
.