Manage Flexible Sync Subscriptions - React Native SDK
On this page
- Prerequisites
- Align Subscriptions with Backend App
- Manage Your Subscriptions
- Access Subscriptions
- Add a Subscription
- Configure a Realm with Initial Subscriptions
- Check the Status of Subscriptions
- Subscription State "Complete"
- 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 Subscriptions
- Flexible Sync RQL Limitations
- Unsupported Query Operators in Flexible Sync
- List Queries
- Embedded or Linked Objects
Flexible Sync uses subscriptions and permissions to determine which
data to sync with your App Services App using Atlas Device Sync. The @realm/react
library streamlines permissions
and queries.
Note
Flexible Sync Prerequisites
Enabling Flexible Sync in your App requires a non-sharded Atlas cluster running MongoDB 5.0 or greater
Prerequisites
Before you can add Flexible Sync subscriptions to a React Native client, you must:
Configure Flexible Sync on the Atlas App Services backend
Authenticate a user in the client
Configure a synced Realm in the client
Align Subscriptions with Backend App
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.
Manage Your Subscriptions
In your client application, use the Realm.subscriptions property 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()
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.
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.
Access Subscriptions
Within a <RealmProvider>
that's configured for Flexible Sync, you can access
a collection of all subscriptions for your app. This is called a
SubscriptionSet.
import React, {useEffect} from 'react'; // get realm context from createRealmContext() import {RealmContext} from '../RealmConfig'; import {Text, FlatList} from 'react-native'; const {useRealm, useQuery} = RealmContext; function SubscriptionManager() { const realm = useRealm(); // Returns a subscription set that contains all subscriptions. const allSubscriptions = realm.subscriptions; // Pass object model to useQuery and filter results. // This does not create a subscription. const seenBirds = useQuery('Bird').filtered('haveSeen == true'); useEffect(() => { realm.subscriptions.update(mutableSubs => { // Create subscription for filtered results. mutableSubs.add(seenBirds); }); }); return ( <FlatList data={allSubscriptions} keyExtractor={subscription => subscription.id.toString()} renderItem={({item}) => <Text>{item.name}</Text>} /> ); }
You can use this SubscriptionSet
to add queries to the list of subscriptions and
update existing subscriptions.
Add a Subscription
Compose queries using Realm Query Language and add them to your Flexible Sync subscriptions.
To add a query to a subscription:
Create a transaction by passing a callback function to the SubscriptionSet.update() method of your
SubscriptionSet
. The callback function provides a MutableSubscriptionSet as an argument.Within the callback function, call the MutableSubscriptionSet.add() method to add a query to the subscription.
MutableSubscriptionSet.add()
takes a query
and a
SubscriptionOptions object.
In SubscriptionOptions
, you should name your subscription by adding a string
value to SubscriptionOptions.name
. This makes updating the subscription easier.
If you do not give your subscription a name, the name is set to null
and
the subscription is based on the Realm Query Language
(RQL) string. If the RQL string changes and the subscription is not named, you
won't be able to update your subscription.
When you create a subscription, Realm looks for data matching a query on a specific object type. In your Flexible Sync subscriptions, you can have subscriptions on several different object types or several queries on the same object type.
Add a subscription with Realm.objects(), which you can access with the useRealm()
hook.
If you're using @realm/react
, you can use the useQuery()
hook instead of
Realm.objects()
. However, 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'; // get realm context from createRealmContext() import {RealmContext} from '../RealmConfig'; import {Text, FlatList} from 'react-native'; const {useRealm} = RealmContext; function SubscriptionManager() { const realm = useRealm(); const seenBirds = realm.objects('Bird').filtered('haveSeen == true'); useEffect(() => { realm.subscriptions.update((mutableSubs, realm) => { // Create subscription query const seenBirdsSubQuery = realm .objects('Bird') .filtered('haveSeen == true'); // Create subscription for filtered results. mutableSubs.add(seenBirdsSubQuery, {name: 'seenBirds'}); }); }); return ( <FlatList data={seenBirds} keyExtractor={bird => bird._id.toString()} renderItem={({item}) => <Text>{item._id}</Text>} /> ); }
Configure a Realm with Initial Subscriptions
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 <RealmProvider>
's
SyncConfiguration object.
You cannot use the @realm/react
library hooks useQuery
and useObject
when setting initial subscriptions. Instead, use the Node.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>} /> ); }
Check the Status of Subscriptions
You can check the state of
a SubscriptionSet
to see if the server has acknowledged all subscriptions 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'; // get realm context from createRealmContext() import {RealmContext} from '../RealmConfig'; const {useRealm, useQuery} = RealmContext; function SubscriptionManager() { const realm = useRealm(); useEffect(() => { realm.subscriptions.update((mutableSubs, realm) => { // Create subscription for filtered results. mutableSubs.add(realm.objects('Bird').filtered('haveSeen == true')); }); }); // 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> ); }
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.
Remove Subscriptions
You can remove subscriptions in several ways:
Remove a single subscription with the given query
Remove a single subscription with the given name
Remove all subscriptions on a specific type
Remove all subscriptions
When you remove a subscription query, the server also removes synced data from the client device.
Examples on this page 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 ( // ... ); }
Remove a Subscription by Query
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"')); });
Remove a Subscription by Name
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"); });
Remove a Subscription by Reference
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);
Remove All Subscriptions on an Object Type
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"); });
Remove All Subscriptions
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(); });
Flexible Sync RQL Limitations
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.
Unsupported Query Operators in Flexible Sync
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"
.