Docs Menu
Docs Home
/ /
Atlas Device SDKs
/ /

Manage Sync Subscriptions - React Native SDK

On this page

  • Prerequisites
  • Align Subscriptions with Backend App
  • Subscribe to Queries
  • Subscribe to a Query
  • Wait for a Query Subscription to Sync
  • Unsubscribe from a Query
  • Manually Manage Subscriptions
  • Access All Subscriptions
  • Add a Subscription
  • Configure a Realm with Initial Subscriptions
  • Check the Status of Subscriptions
  • Subscription State "Complete"
  • Update Subscriptions with a New Query
  • Remove Subscriptions
  • Remove a Subscription by Query
  • Remove a Subscription by Name
  • Remove a Subscription by Reference
  • Remove All Subscriptions on an Object Type
  • Remove All Unnamed Subscriptions
  • Remove All Subscriptions
  • Performance Considerations
  • API Efficiency
  • Group Updates for Improved Performance
  • Flexible Sync RQL Requirements and Limitations
  • Indexed Queryable Fields Subscription Requirements
  • Unsupported Query Operators in Flexible Sync
  • List Queries
  • Embedded or Linked Objects
  • Query Size Limit

Flexible Sync uses subscriptions and permissions to determine what data to sync with your App. You must have at least one subscription before you can read from or write to a realm with Flexible Sync enabled. The @realm/react library streamlines permissions and queries for sync subscriptions.

You can add, update, and remove query subscriptions to control what data syncs to the client device. In the Realm.js v12.0.0 and later, you can subscribe to queries instead of or in addition to manually managing subscriptions.

You can't create subscriptions for Data Ingest and asymmetric objects because they only send data to your App Services backend.

Important

Flexible Sync Query Limitations

Flexible Sync subscriptions only support a subset of the RQL query operators. Refer to the Flexible Sync RQL Limitations documentation for information on which operators are not supported.

You need to meet the following requirements before you can use Atlas Device Sync with the React Native SDK:

  • A non-sharded Atlas cluster running MongoDB 5.0 or later.

  • Realm.js v10.12.0 or later.

Before you can add Flexible Sync subscriptions to a React Native client, you must:

  1. Configure Flexible Sync on the backend

  2. Initialize the app client

  3. Authenticate a user in the client

  4. Configure a synced Realm

Your client-side subscription queries must align with the Device Sync configuration in your backend App Services App.

Subscription queries return all objects of a type. You can filter results with a Realm Query Language query that includes one or more queryable fields.

To learn more about configuring queryable fields, refer to Queryable Fields in the App Services documentation.

To learn more about the limitations of using Realm Query Language with Flexible Sync, refer to the Flexible Sync RQL Limitations section.

New in version realm@12.0.0.

realm@12.0.0 adds experimental APIs that subscribe to and unsubscribe from a query's results. These APIs abstract away the details of manually adding and removing subscriptions.

For all subscriptions, you need an authenticated user and a Flexible Sync realm.

Changed in version realm@12.3.0: Geospatial data supported in Atlas Device Sync

In the Realm.js v12.3.0 and later, you can create subscriptions to geospatial queries. If you try to subscribe to a geospatial query with an older version of the SDK, you will receive a server error with a compensating write.

For more information, refer to Geospatial - React Native SDK.

We recommend that you name your subscriptions. This makes finding and managing your subscriptions easier. Subscription names must be unique. Trying to add a subscription with the same name as an existing subscription overwrites the existing subscription.

To subscribe to a query:

  1. Query for the objects that you want to read and write.

  2. Call subscribe() on the query results to create a sync subscription for objects matching the query.

  3. Pass a SubscriptionOptions object that contains the name property to subscribe().

import React, {useEffect, useState} from 'react';
import {useRealm, useQuery} from '@realm/react';
import {View, Text, FlatList} from 'react-native';
import {Bird} from '../../models';
import {Subscription} from 'realm/dist/bundle';
export const BasicSubscription = () => {
const realm = useRealm();
// Get all local birds that have not been seen yet.
const seenBirds = useQuery(Bird, collection =>
collection.filtered('haveSeen == true'),
);
const [seenBirdsSubscription, setSeenBirdsSubscription] =
useState<Subscription | null>();
useEffect(() => {
// Create an async function so that we can `await` the
// promise from `.subscribe()`.
const createSubscription = async () => {
await seenBirds.subscribe({
name: 'Birds I have seen',
});
};
createSubscription().catch(console.error);
// Get the subscription...
const subscription = realm.subscriptions.findByName('Birds I have seen');
// ... and set it to a stateful variable or manage it in `useEffect`.
setSeenBirdsSubscription(subscription);
}, []);
return (
// Work with the subscribed results list or modify the subscription...
<></>
);
};

Most of the time, you should give your subscriptions a name. If you don't, the name is set to null.

If you use filtered() on an unnamed query subscription, the subscription identifier is based on the filtered query. This means that every time your query string changes, subscribe() will create a new subscription.

API Reference

When you subscribe to a query's results, the results do not contain objects until synced data is downloaded. When you do need to wait for synced objects to finish downloading, configure the waitForSync option.

This example uses the FirstTime option, which is the default behavior. A subscription with FirstTime behavior only waits for sync to finish when a subscription is first created.

import React, {useEffect, useState} from 'react';
import {BSON, WaitForSync} from 'realm';
import {useRealm, useQuery} from '@realm/react';
import {View, Text, Button, TextInput, FlatList} from 'react-native';
import {Bird} from '../../models';
import {Subscription} from 'realm/dist/bundle';
export const WaitFirstTime = () => {
const realm = useRealm();
const [birdName, setBirdName] = useState('Change me!');
// Get local birds that have been marked as "haveSeen".
const seenBirds = useQuery(Bird, collection =>
collection.filtered('haveSeen == true'),
);
const [seenBirdsSubscription, setSeenBirdsSubscription] =
useState<Subscription | null>();
useEffect(() => {
const createSubscription = async () => {
// Only wait for sync to finish on the initial sync.
await seenBirds.subscribe({
behavior: WaitForSync.FirstTime,
name: 'First time sync only',
});
};
createSubscription().catch(console.error);
// Get the subscription...
const subscription = realm.subscriptions.findByName('First time sync only');
// ... and set it to a stateful variable or manage it in `useEffect`.
setSeenBirdsSubscription(subscription);
}, []);
return (
// Work with the subscribed results list or modify the subscription...
<></>
);
};

The other supported WaitForSync options are:

  • Always: Wait to download matching objects every time your app launches. The app must have an internet connection at every launch.

  • Never: Never wait to download matching objects. The app needs an internet connection for the user to authenticate the first time the app launches, but can open offline on subsequent launches using cached credentials.

You can optionally specify a timeout value to limit how long the sync download runs:

export const AlwaysWait = () => {
const realm = useRealm();
// Get all local birds that have not been seen yet.
const unSeenBirds = useQuery(Bird, collection =>
collection.filtered('haveSeen == false'),
);
const [unSeenBirdsSubscription, setUnseenBirdsSubscription] =
useState<Subscription | null>();
useEffect(() => {
const createSubscription = async () => {
// Add subscription with timeout.
// If timeout expires before sync is completed, currently-downloaded
// objects are returned and sync download continues in the background.
await unSeenBirds.subscribe({
behavior: WaitForSync.Always,
name: 'Always wait',
timeout: 500,
});
};
createSubscription().catch(console.error);
// Get the subscription...
const subscription = realm.subscriptions.findByName('Always wait');
// ... and set it to a stateful variable or manage it in `useEffect`.
setUnseenBirdsSubscription(subscription);
}, []);
return (
// Work with the subscribed results list or modify the subscription...
<></>
);
};

API Reference

Subscriptions persist across user sessions unless you unsubscribe from them. You can unsubscribe from a query's results using unsubscribe().

This removes the subscription from the list of active subscriptions, similar to manually removing a subscription.

A results list may still contain objects after calling unsubscribe() if another subscription exists that contains overlapping objects.

When you call unsubscribe(), the associated subscription is removed. Subscriptions are removed by name. If they don't have a name, unsubscribe() removes any queries that exactly match the one you call unsubscribe() on.

The unsubscribe() method returns before objects matching the removed subscription are deleted from the realm. Sync continues in the background based on the new set of subscriptions.

import React, {useEffect, useState} from 'react';
import {useRealm, useQuery} from '@realm/react';
import {View, Text, Button} from 'react-native';
import {Bird} from '../../models';
import {Subscription} from 'realm/dist/bundle';
export const Unsubscribe = () => {
const realm = useRealm();
const birds = useQuery(Bird);
const unsubscribeFromQuery = () => {
birds.unsubscribe();
};
return (
<View>
<Button
title="Unsubscribe"
onPress={() => {
unsubscribeFromQuery();
}}
/>
</View>
);
};

API Reference

You can use the Subscriptions API to manually manage a set of subscriptions to specific queries on queryable fields.

You can use the Realm.subscriptions API to manage a set of subscriptions to specific queries on queryable fields.

If you're using @realm/react, you can manage realm subscriptions inside of a properly-configured RealmProvider. The useRealm() hook gives you access to the currently-opened realm.

You can do the following with your subscriptions:

When the data matches the subscription, and the authenticated user has the appropriate permissions, Device Sync syncs the backend data with the client app.

When you create a subscription, Realm looks for data matching a query on a specific object type. You can have subscriptions on several 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 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.

Within a RealmProvider configured for Flexible Sync, you can access a SubscriptionSet. A SubscriptionSet is a collection of all subscriptions for your app.

import React, {useEffect, useState} from 'react';
import {Text, FlatList} from 'react-native';
import {useRealm, useQuery} from '@realm/react';
import {Bird} from '../Models/Bird';
function SubscriptionManager() {
const realm = useRealm();
// Pass object model to useQuery and filter results.
// This does not create a subscription.
const seenBirds = useQuery(Bird, birds => {
return birds.filtered('haveSeen == true');
});
const [subscriptions, setSubcriptions] = useState<
App.Sync.SubscriptionSet | undefined
>();
useEffect(() => {
const createSubscription = async () => {
// Create subscription for filtered results.
await realm.subscriptions.update(mutableSubs => {
mutableSubs.add(seenBirds, {name: 'seen birds'});
});
};
createSubscription().catch(console.error);
// Set to state variable.
setSubcriptions(realm.subscriptions);
}, []);
return (
<FlatList
data={subscriptions}
keyExtractor={subscription => subscription.id.toString()}
renderItem={({item}) => <Text>{item.name}</Text>}
/>
);
}

API Reference

In the following example, completed and progressMinutes have been set as queryable fields in an App Services App. In the client code, we create filtered queries and then subscribe to their results:

  • Completed tasks

  • Completed tasks that have taken over 120 progressMinutes

Note that useQuery() needs an active subscription to return results. If no subscription has been added yet, useQuery() returns an empty result, which is not a valid query for MutableSubscriptionSet.add().

import React, {useEffect} from 'react';
import {Text, FlatList} from 'react-native';
import {useRealm, useQuery} from '@realm/react';
function SubscriptionManager() {
const realm = useRealm();
const seenBirds = useQuery(Bird, birds => {
return birds.filtered('haveSeen == true');
});
useEffect(() => {
realm.subscriptions.update(
(mutableSubs: Realm.App.Sync.MutableSubscriptionSet) => {
// Create subscription for filtered collection.
mutableSubs.add(seenBirds, {name: 'seenBirds'});
},
);
});
return (
<FlatList
data={seenBirds}
keyExtractor={item => item._id.toString()}
renderItem={({item}) => <Text>{item._id.toString()}</Text>}
/>
);
}

You must have at least one subscription before you can read from or write to a Flexible Sync realm. Initial subscriptions let you define subscriptions when you configure a synced realm.

To open a synced realm with initial subscriptions, add an initialSubscriptions property to a RealmProvider's sync configuration.

You can't use the @realm/react library hooks useQuery and useObject when setting initial subscriptions. Instead, use the Realm.js read and write operations.

import React from 'react';
import {AppProvider, UserProvider} from '@realm/react';
// get realm context from createRealmContext()
import {RealmContext} from '../RealmConfig';
import {Text, FlatList} from 'react-native';
const {RealmProvider, useQuery} = RealmContext;
function AppWrapper() {
return (
<AppProvider id={APP_ID}>
<UserProvider fallback={LogIn}>
<RealmProvider
sync={{
flexible: true,
initialSubscriptions: {
update(subs, realm) {
subs.add(realm.objects('Turtle'));
},
},
onError: console.log,
}}>
<SubscriptionManager />
</RealmProvider>
</UserProvider>
</AppProvider>
);
}
function SubscriptionManager() {
// Pass object model to useQuery to get all objects of type `Turtle`.
// These results automatically update with changes from other devices
// because we created a subscription with `initialSubscriptions`.
const allTurtles = useQuery('Turtle');
return (
<FlatList
data={allTurtles}
keyExtractor={turtle => turtle._id.toString()}
renderItem={({item}) => <Text>{item._id}</Text>}
/>
);
}

By default, initial subscriptions are only created the first time a realm is opened. If your app needs to rerun this initial subscription every time the app starts, you can set rerunOnOpen to true. You might need to do this to rerun dynamic time ranges or other queries that require a recomputation of static variables for the subscription.

API Reference

You can check the subscription state to see if the server has acknowledged the subscription and the device has downloaded the data locally.

You can use subscription state to:

  • Trigger error handling

  • Show if the transaction is pending or has completed

  • Find out when a subscription set is superseded, and you should obtain a new instance of the subscription set to write a subscription change

import React, {useEffect} from 'react';
import {Text, View} from 'react-native';
import {useRealm, useQuery} from '@realm/react';
import {Bird} from '../Models/Bird';
function SubscriptionManager() {
const realm = useRealm();
const seenBirds = useQuery(Bird, birds => {
return birds.filtered('haveSeen == true');
});
useEffect(() => {
realm.subscriptions.update(
(mutableSubs: Realm.App.Sync.MutableSubscriptionSet) => {
// Create subscription for filtered collection.
mutableSubs.add(seenBirds, {name: 'seenBirds'});
},
);
});
// Returns state of all subscriptions, not individual subscriptions.
// In this case, it's just the subscription for `Bird` objects where
// `haveSeen` is true.
const allSubscriptionState = realm.subscriptions.state;
return (
<View>
<Text >
Status of all subscriptions: {allSubscriptionState}
</Text>
</View>
);
}

New in version realm@12.0.0.

Realm.js v12.0.0 added the SubscriptionSetState enum that you can use to get the status of a subscription.

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.

You can update a named subscription with a new query. To update a subscription's query, pass the new query and a subscription option with the name of the subscription that you want to update to the MutableSubscriptionSet.add() method. Like adding a new subscription, you must update a subscription within a transaction by calling subscriptions.update().

The following example, redefines long-running tasks as any tasks that take more than 180 minutes.

realm.subscriptions.update((mutableSubs) => {
mutableSubs.add(
tasks.filtered('status == "completed" && progressMinutes > 180'),
{
name: "longRunningTasksSubscription",
}
);
});

Note

Attempting to update a subscription that has the SubscriptionOptions.throwOnUpdate field set to true throws an exception.

API Reference

Subscription sets persist across sessions, even if you no longer include the subscription in your code. Subscription information is stored in the synced realm's database file. You must explicitly remove a subscription for it to stop attempting to sync matching data.

You can remove subscriptions in the following ways:

  • Remove a single subscription with a specific query

  • Remove a single subscription with a specific name

  • Remove all subscriptions to a specific object model

  • Remove all unnamed subscriptions

  • Remove all subscriptions

When you remove a subscription query, the server also removes synced data from the client device.

Examples in this section assume you're working with @realm/react and a properly-configured RealmProvider.

import {useEffect} from 'react';
// get realm context from createRealmContext()
import {RealmContext} from '../RealmConfig';
const {useRealm} = RealmContext;
function SubscriptionManager() {
const realm = useRealm();
useEffect(() => {
realm.subscriptions.update(mutableSubs => {
// Remove subscription for object type `Turtle`,
// which we added in `initialSubscriptions`.
mutableSubs.removeByObjectType('Turtle');
});
});
return (
// ...
);
}

You can remove a specific subscription by query by executing a transaction on the subscriptions set. Pass the query to MutableSubscriptionSet.remove() within a write transaction.

realm.subscriptions.update((mutableSubs) => {
// remove a subscription with a specific query
mutableSubs.remove(tasks.filtered('owner == "Ben"'));
});

To remove a specific subscription by name, execute a transaction on the subscriptions set. Within the transaction, pass the name to MutableSubscriptionSet.removeByName().

realm.subscriptions.update((mutableSubs) => {
// remove a subscription with a specific name
mutableSubs.removeByName("longRunningTasksSubscription");
});

If you have a reference to a subscription, you can remove that subscription. To do so, execute a transaction on the subscriptions set. Within the transaction, pass the reference variable to MutableSubscriptionSet.removeSubscription().

let subscriptionReference;
realm.subscriptions.update((mutableSubs) => {
subscriptionReference = mutableSubs.add(realm.objects("Task"));
});
// later..
realm.subscriptions.removeSubscription(subscriptionReference);

To remove all subscriptions on a specific object type, execute a transaction on the subscriptions set. Within the transaction, pass the object type as a string to MutableSubscriptionSet.removeByObjectType().

realm.subscriptions.update((mutableSubs) => {
mutableSubs.removeByObjectType("Team");
});

New in version realm@v12.0.0.

You may want to remove unnamed subscriptions that are transient or dynamically generated, but leave named subscriptions in place.

You can remove all unnamed subscriptions from the subscription set by calling .removeUnnamed() on mutableSubs. .removeUnnamed() returns the number of unnamed subscriptions removed.

// Remove unnamed subscriptions.
let numberRemovedSubscriptions = 0;
await realm.subscriptions.update((mutableSubs) => {
numberRemovedSubscriptions = mutableSubs.removeUnnamed();
});

API Reference

To remove all subscriptions from the subscriptions set, execute a transaction on the subscriptions set. Call MutableSubscriptionSet.removeAll() within a write transaction.

realm.subscriptions.update((mutableSubs) => {
mutableSubs.removeAll();
});

Managing multiple subscriptions with the subscribe() and unsubscribe() APIs described in the Subscribe to Queries section is less efficient than performing batch updates when you manually manage subscriptions.

For better performance when making multiple subscription changes, use the subscriptions API to update all the subscriptions in a single transaction. To learn how, see Manually Manage Subscriptions.

Every write transaction for a subscription set has a performance cost. If you need to make multiple updates to a Realm object during a session, consider keeping edited objects in memory until all changes are complete. This improves sync performance by only writing the complete and updated object to your realm instead of every change.

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 == or IN comparison against a constant at least once in the subscription query. For example, user_id == $0, "641374b03725038381d2e1fb" or store_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 example store_id IN {1,2,3} OR region=="Northeast" is invalid because it uses OR instead of AND. Similarly, store_id == 1 AND active_promotions < 5 OR num_employees < 10 is invalid because the AND 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 or truepredicate are invalid because they do not contain the indexed queryable field.

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.

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"

Flexible Sync does not support querying on properties in Embedded Objects or links. For example, obj1.field == "foo".

The size limit for any given query subscription in your subscription set is 256 kB. Exceeding this limit results in a LimitsExceeded Error.

Back

Configure a Synced Realm