Realm
Realm
• Reactive: query the current state of data and subscribe to state changes
like the result of a query, or even changes to a single object.
This page explains some of the implementation details and inner workings of
Realm and Device Sync. This page is for you if you are:
This explanation begins with a deep dive into database internals, continues
with a high-level introduction to some of the features of Realm, and wraps up
with some of the differences of working with Device Sync and the local version
of Realm.
Database Internals
Realm uses a completely unique database engine, file format, and design. This
section describes some of the high-level details of those choices. This section
applies to both the device-local version of Realm as well as the networked
Device Sync version. Differences between the local database and the
synchronized database are explained in the Atlas Device Sync section.
Realm uses a zero-copy design to make queries faster than an ORM, and often
faster than raw SQLite.
Realm Files
Realm persists data in files saved on device storage. The database uses
several kinds of file:
Realm files contain object data with the following data structures: Groups,
Tables, Cluster Trees, and Clusters. Realm organizes these data structures
into a tree structure with the following form:
• Each class in the realm schema corresponds to a Table within the top-
level Group.
• Leaves on the Cluster Tree are called Clusters. Each contains a range of
objects sorted by key value.
• Each column contains data for a single property for multiple instances
of a given object. Columns are arrays of data with uniformly sized
values.
Because of copy-on-write, older copies of data remain valid, since all of the
references in those copies still point to other valid data. Realm leverages this
fact to offer multiple versions of data simultaneously to different threads in
client applications. Most applications tie data refreshes to the repaint cycle of
the looper thread that controls the UI, since data only needs to refresh as often
as the UI does. Longer-running procedures on background threads, such as
large write operations, can work with a single version of data for a longer
period of time before committing their changes.
Memory Mapping
Writes use memory mapping to avoid copying data back and forth from
memory to storage. Accessors and mutators read and write to disk via
memory mapping. As a result, object data is never stored on the stack or heap
of your app. By default, data is memory-mapped as read-only to prevent
accidental writes.
Realm uses operating system level paging, trusting each operating system to
implement memory mapping and persistence better than a single library could
on its own.
Compaction
Realm automatically reuses free space that is no longer needed after database
writes. However, realm files never shrink automatically, even if the amount of
data stored in your realm decreases significantly. Compact your realm to
optimize storage space and decrease file size if possible.
You should compact your realms occasionally to keep them at an optimal size.
You can do this manually, or by configuring your realms to compact on
launch. However, Realm reclaims unused space for future writes, so
compaction is only an optimization to conserve space on-device.
ACID Compliance
Realm guarantees that transactions are ACID compliant. This means that all
committed write operations are guaranteed to be valid and that clients don't
see transient states in the event of a system crash. Realm complies with ACID
with the following design choices:
• Isolation: allows only one writer at a time. This ensures thread safety
between transactions.
Features
Queries
You can query Realm using platform-native queries or a raw query language
that works across platforms.
Encryption
Realm supports on-device realm encryption. Since memory mapping does not
support encryption, encrypted realms use a simulated in-library form of
memory mapping instead.
NOTE
Realm forbids opening the same encrypted realm from multiple processes.
Attempting to do so will throw the error: "Encrypted interprocess sharing is
currently unsupported."
Indexes
Schemas
Every realm object has a schema. That schema is defined via a native object in
your SDK's language. Object schemas can include embedded lists and
relations between object instances.
Each realm uses a versioned schema. When that schema changes, you must
define a migration to move object data between schema versions. Non-
breaking schema changes, also referred to as additive schema changes, do
not require a migration. After you increment the local schema version, you can
begin using the updated schema in your app. Breaking schema changes, also
called destructive schema changes, require a migration function.
Device Sync relies on a worker client that communicates with your application
backend in a dedicated thread in your application. Additionally, synced realms
keep a history of changes to contained objects. Sync uses this history to
resolve conflicts between client changes and backend changes.
Applications that use Device Sync define their schema on the backend
using JSON Schema. Client applications must match that backend schema to
synchronize data. However, if you prefer to define your initial schema in your
application's programming language, you can use Development Mode to
create a backend JSON Schema based on native SDK objects as you write
your application. However, once your application is used for production
purposes, you should alter your schema using JSON Schema on the backend.
The Realm data model is similar to both relational and document databases
but has distinct differences from both. To underscore these differences, it's
helpful to highlight what a realm is not:
A realm is not a single, application-wide database.
Applications based on other database systems generally store all of their data in
a single database. Apps often split data across multiple realms to organize data
more efficiently and to enforce access controls.
A realm is not a relational table.
Normalized tables in relational databases only store one type of information,
such as street addresses or items in a store inventory. A realm can contain any
number of object types that are relevant to a given domain.
A realm is not a collection of schemaless documents.
Document databases don't necessarily enforce a strict schema for the data in
each collection. While similar to documents in form, every Realm object
conforms to a schema for a specific object type in the realm. An object cannot
contain a property that is not described by its schema.
Live Queries
You can query a realm to find objects based on their type and the
values of their properties. Objects and queries always reflect the
latest state of an object and emit notifications that can update your
app whenever data changes.
TIP
For code examples that show how to read and filter Realm objects
with the React Native SDK, see Read Operations.
Live Objects
NOTE
TIP
See also:
Collections
A results collection represents all objects in a realm that match a
query operation. In general you can work with a collection like a
regular JavaScript array but collections don't actually hold
matching Realm objects in memory. Instead they reference the
matched objects, which themselves map directly to data in the
realm file.
NOTE
Change Notifications
TIP
For code examples that show how to define, register, and clean up
change notification listeners with the React Native SDK, see React
to Changes.
TIP
See also:
Set up your project with React Native and Realm. To get started, install the
Realm React Native SDK.
You can configure your realm to do things like populate initial data on load, be
encrypted, and more. To begin working with your data, configure and open a
realm.
You can create, read, update, and delete objects from a realm. Construct
complex queries to filter data in a realm.
React to Changes
Realm's live objects mean that your data is always up-to-date. Register a
change listener to react to changes and perform logic like updating your
UI.
Install Realm for React Native
Prerequisites
NOTE
For React Native version 0.64 and below, you must take additional steps
to build your application when using Mac Catalyst.
To use Realm with Expo, upgrade to Expo SDK v48. Check out
the compatibility chart to determine which version of the Expo SDK is
compatible with specific Realm React Native SDK versions.
Installation
Select the tab below that corresponds to your React Native version. Follow the
steps to create a React Native project and add the Realm React Native SDK to
it.
React Native v.60+
Older React Native Versions
1
Change to the project directory that the previous command just created:
cd MyRealmApp
2
In your React Native project directory, add Realm to your project with the
following command:
To use Hermes, your app must use Realm v11 or later and React Native 0.70.0
or later
We recommend that you use Hermes with Realm. However, Realm also
supports the JavaScriptCore (JSC) engine if your app requires it.
Existing apps that currently use JSC can enable Hermes separately for
Android and iOS. To learn how, see the Using Hermes guide in the React
Native docs.
4
Resolve CocoaPods Dependencies
For the iOS app, fetch the CocoaPods dependencies with the following
commands from your React Native project directory:
TypeScript is a superset of JavaScript that adds static type checking and other
features intended to make application-scale development more robust. If you'd
like to use TypeScript in your project, follow the React Native team's
official TypeScript and React Native guide. Realm supports TypeScript natively
and integrates easily into a TypeScript project.
NOTE
In your React Native project directory, add @realm/react to your project with
the following command:
In development, the apps read their React source code as a bundle from a
local bundle server. To run the bundle server, use the following command in
your React Native project directory:
npm start
With the bundle server running, you can now launch the Android and iOS
apps:
• To run the iOS app, use Xcode to open the .xcworkspace file in
the ios directory. If you did not use CocoaPods during setup, open
the .xcodeproj file in the ios directory instead. Once you have
opened the project, click Run.
Import Realm
Add the following line to the top of your source files where you want to use
Realm:
Then you can pass the class itself to the schema property of
the Configuration object when opening a realm.
Every property in a Realm object has a strongly defined data type. A property's
type can be a primitive data type or an object type defined in the same realm.
The type also specifies whether the property contains a single value or a list of
values.
To specify that a field contains a list of a primitive value type, append [] to the
type name.
In the following example of a Person class, the age and birthday properties
are both optional.
NOTE
If an object type has a primary key, then all objects of that type must include
the primary key property with a unique value among objects of the same type
in a realm. An object type can have only one primary key. You cannot change
the primary key property for an object type after any object of that type is
added to a realm, and you cannot modify an object's primary key value.
In the following example of a Task class, we specify the _id property as the
primary key.
TypeScript
JavaScript
1 class Task extends Realm.Object {
2 static schema = {
3 name: 'Task',
4 properties: {
5 _id: 'int',
6 name: 'string',
7 priority: 'int?',
8 progressMinutes: 'int?',
9 assignee: 'Person?',
10 },
11 primaryKey: '_id',
12 };
13 }
Index a Property
If you frequently run read operations based on a specific property, you can
index the property to optimize performance. Realm supports indexing for
string, integer, boolean, Date, UUID, and ObjectId properties.
NOTE
For more information on querying FTS indexes, see Filter with Full-Text
Search.
To create an FTS index, set the indexed type to 'full-text'. This enables
full-text queries on the property. In the following example, we set the indexed
type for the name property to 'full-text':
To define a default value, set the value of the property to an object with
a type field and a default field.
In the following example of a Car class, we define a miles property with a
default value of 0.
In Realm.js v11.1.0 and later, you can use a function to define a dynamic
default value, like the timestamp property in the example below.
TypeScript
JavaScript
1 class Car extends Realm.Object {
2 static schema = {
3 name: 'Car',
4 properties: {
5 make: 'string',
6 model: 'string',
7 miles: {type: 'int', default: 0},
8 timestamp: {
9 type: 'int',
10 default: () => Math.round(new Date().getTime() / 1000),
11 },
12 },
13 };
14 }
Remap a Property
TypeScript
JavaScript
1 class Employee extends Realm.Object {
2 static schema = {
3 name: 'Employee',
4 properties: {
5 _id: 'string',
6 first_name: {type: 'string', mapTo: 'firstName'},
7 },
8 primaryKey: '_id',
9 };
10 }
Define an Asymmetric Object
If you are using Flexible Sync and need to sync a collection unidirectionally
from your decive to your Atlas database, you can set
the asymmetric property on your object schema.
TypeScript
JavaScript
class WeatherSensor extends Realm.Object{
static schema = {
name: 'WeatherSensor',
// sync WeatherSensor objects one way from your device
// to your Atlas database.
asymmetric: true,
primaryKey: '_id',
properties: {
_id: 'objectId',
deviceId: 'string',
temperatureInFahrenheit: 'int',
barometricPressureInHg: 'float',
windSpeedInMph: 'float',
},
};
}
NOTE
We recommend creating Realm objects with Realm.create(), but you can also
use the new operator for your object model's class.
If you use new, you must add your class as a generic, along with any required
properties, when extending Realm.Object. This enables full TypeScript
support for your object model, including type errors when required fields are
not defined.
The Realm React Native SDK and @realm/react package provide many
configuration options for your realm.
How you configure your realm determines the capabilities of your realm and
how you work with your data. This page contains information about how to
configure your realm in various ways.
Prerequisites
When RealmProvider is rendered, it opens the realm. This means that the
provider renders successfully or its child components can't access the realm.
Configuration Options
You can configure RealmProvider by setting props that match the properties
of a Configuration object. You can also set fallback and realmRef props.
• realmRef
Used with useRef to expose the configured realm to processes outside
of RealmProvider. This can be useful for things like a client reset fallback.
• fallback
Rendered while waiting for the realm to open. Local realms usually open fast
enough that the fallback prop isn't needed.
To create a realm that runs entirely in memory without being written to a file,
pass true to the inMemory prop on your RealmProvider:
In-memory realms may use disk space if memory is running low, but files
created by an in-memory realm are deleted when you close the realm.
Encrypt a Realm
To open a realm that synchronizes data with Atlas using Device Sync, refer
to Open a Synced Realm.
You can open more than one realm at a time by creating additional Context
objects using createRealmContext().
TypeScript
JavaScript
import React from 'react';
import {
Realm,
AppProvider,
UserProvider,
createRealmContext,
} from '@realm/react';
class SharedDocument extends Realm.Object {
static schema = {
name: 'SharedDocument',
properties: {
_id: 'objectId',
owner_id: 'objectId',
title: 'string',
createdDate: 'date',
},
primaryKey: '_id',
};
}
class LocalDocument extends Realm.Object {
static schema = {
name: 'LocalDocument',
properties: {
_id: 'objectId',
name: 'string',
createdDate: 'date',
},
};
}
// Create Shared Document context object.
const SharedRealmContext = createRealmContext({
schema: [SharedDocument],
});
// Create Local Document context object.
const LocalRealmContext = createRealmContext({
schema: [LocalDocument],
});
You need to extract providers and hooks from each Context object. You
should namespace the providers and hooks using destructuring. This makes it
easier to reason about the realm you're working with.
After extracting a Context object's providers and hooks, you can use them in
your app's components. Child components inside of extracted providers have
access to extracted hooks.
function TwoRealmsWrapper() {
return (
<View>
<AppProvider id={APP_ID}>
<UserProvider fallback={LogIn}>
{/* This is a Flexible Sync realm. */}
<SharedDocumentRealmProvider sync={{flexible: true}}>
<AppSectionOne />
</SharedDocumentRealmProvider>
</UserProvider>
</AppProvider>
{/* This is a separate local-only realm. */}
<LocalDocumentRealmProvider>
<AppSectionTwo />
</LocalDocumentRealmProvider>
</View>
);
}
function AppSectionOne() {
const realm = useSharedDocumentRealm();
// Work with shared documents...
}
function AppSectionTwo() {
const realm = useLocalDocumentRealm();
// Work with local documents...
}
After a realm has been created on a device, you don't need to always pass in a
schema to access the realm. Instead, you can use RealmProvider without
passing any object models to its schema property. The realm's schema is
derived from the existing realm file at Realm.defaultPath.
Accessing a realm without providing a schema only works for local realms.
You must always pass a schema when using a Synced realm.
@realm/react has providers and hooks that simplify working with your non-
sync realm and its data.
RealmProviderA wrapper that exposes a realm to its child See Configure a Realm Without
components, which have access to hooks
that let you read, write, and update data.
useRealm Returns the instance of the Realm opened const realm = useRealm();
by the RealmProvider.
useObject Returns an object (Realm.Object<T>) from const myTask = useObject(Task,
a given type and value of primary key.
Updates on any changes to the returned
object. Returns null if the object either
doesn't exists or has been deleted.
useQuery Returns a collection of objects const tasks = useQuery(Task);
(Realm.Results<T & Realm.Object T>)
from a given type. Updates on any changes
to any object in the collection. Returns an
empty array if the collection is empty.
Realm Files
Realm stores a binary encoded version of every object and type in a realm in a
single .realm file. The file is located at a specific path that you define when
you open the realm.
TIP
NOTE
Auxiliary Realm Files
Realm creates additional files for each realm. To learn more about these files,
see Realm Internals.
WARNING
If you delete a realm file or any of its auxiliary files while one or more
instances of the realm are open, you might corrupt the realm or disrupt sync.
You may safely delete these files when all instances of a realm are closed.
Before you delete a realm file, make sure that you back up any important
objects as you will lose all unsynced data in the realm.
You can encrypt the realm database file on disk with AES-256 + SHA-2 by
supplying a 64-byte encryption key when opening a realm.
WARNING
You must pass the same encryption key every time you open the encrypted
realm. If you don't provide a key or specify the wrong key for an encrypted
realm, the Realm SDK throws an error.
Apps should store the encryption key securely, typically in the target
platform's secure key/value storage, so that other apps cannot read the key.
Performance Impact
Realm only encrypts the data on the device and stores the data unencrypted in
your Atlas data source. Any users with authorized access to the Atlas data
source can read the data, but the following still applies:
• Users must have the correct read permissions to read the synced data.
You can also enable Customer Key Management to encrypt stored Atlas data
using your cloud provider's key (e.g. AWS KMS, Azure Key Vault, Google
Cloud KMS).
If you need unique keys for each user of your application, you can use an
OAuth provider or use one of the Realm authentication providers and
an authentication trigger to create a 64-bit key and store that key in a user
object.
Starting with Realm React Native SDK version 11.8.0, Realm supports opening
the same encrypted realm in multiple processes.
If your app uses Realm React Native SDK version 11.7.0 or earlier, attempting
to open an encrypted realm from multiple processes throws this
error: Encrypted interprocess sharing is currently unsupported.
Example
The following code demonstrates how to generate an encryption key and open
an encrypted realm:
This page demonstrates how to use Realm using the React Native SDK.
The React Native SDK documentation uses the @realm/react npm package
for examples and describing concepts.
After installing the realm and @realm/react packages, there are a few more
things to set up before you can access your realm and work with local data:
Your application's object models define the data types that you can store
within a realm. Each object model becomes a Realm object type.
TypeScript
JavaScript
// Define your object model
class Profile extends Realm.Object {
static schema = {
name: 'Profile',
properties: {
_id: 'objectId',
name: 'string',
},
primaryKey: '_id',
};
}
Configure a Realm
Before you can work with data, you need to configure a realm. This means you
need to set up context and providers from @realm/react. To learn more, refer
to Configure a Realm.
After you have a data model and a configured realm, you can create, read,
update, or delete Realm objects.
You must nest any components that perform these operations inside of
a RealmProvider. The useRealm(), useQuery(), and useObject() hooks
enable you to perform read and write operations in your realm.
Find, Sort, and Filter Objects
After finding a collection, you can filter or sort the results using Realm Query
Language (RQL).
TypeScript
JavaScript
import React, {useState} from 'react';
import Realm from 'realm';
import {createRealmContext} from '@realm/react';
// Define your object model
class Profile extends Realm.Object {
static schema = {
name: 'Profile',
properties: {
_id: 'objectId',
name: 'string',
},
primaryKey: '_id',
};
}
// Create a configuration object
const realmConfig = {
schema: [Profile],
};
// Create a realm context
const {RealmProvider, useObject, useQuery} = createRealmContext(realmConfig);
// Expose a realm
function AppWrapper() {
return (
<RealmProvider>
<FindSortFilterComponent objectPrimaryKey={YOUR_PRIMARY_KEY} />
</RealmProvider>
);
}
const FindSortFilterComponent = ({objectPrimaryKey}) => {
const [activeProfile, setActiveProfile] = useState();
const [allProfiles, setAllProfiles] = useState();
After accessing the realm with useRealm(), you can create, modify, and
delete objects inside of the realm in a Realm.write() transaction block.
TypeScript
JavaScript
import React from 'react';
import Realm from 'realm';
import {createRealmContext} from '@realm/react';
// Define your object model
class Profile extends Realm.Object {
static schema = {
name: 'Profile',
properties: {
_id: 'objectId',
name: 'string',
},
primaryKey: '_id',
};
}
// Create a configuration object
const realmConfig = {
schema: [Profile],
};
// Create a realm context
const {RealmProvider, useRealm, useObject, useQuery} =
createRealmContext(realmConfig);
// Expose a realm
function AppWrapper() {
return (
<RealmProvider>
<RestOfApp objectPrimaryKey={YOUR_PRIMARY_KEY} />
</RealmProvider>
);
}
function RestOfApp({objectPrimaryKey}) {
const [selectedProfileId, setSelectedProfileId] = useState(objectPrimaryKey);
realm.write(() => {
profileToChange.name = newName;
});
};
// ... rest of component
}
Create Objects
To create a new Realm object, specify the object type, pass in the object's
initial values, and add it to the realm in a write transaction block.
TypeScript
JavaScript
const addProfile = (name) => {
realm.write(() => {
realm.create('Profile', {
name: name,
_id: new Realm.BSON.ObjectId(),
});
});
};
Update Objects
TypeScript
JavaScript
const changeProfileName = (profile, newName) => {
realm.write(() => {
profile.name = newName;
});
};
Delete Objects
TypeScript
JavaScript
const deleteProfile = (profile) => {
realm.write(() => {
realm.delete(profile);
});
};
After getting your local-only realm running, you can add Atlas Device Sync so
that your realm's data can sync with a MongoDB Atlas cluster and other client
devices.
Prerequisites
To use App Services features, such as authentication and Device Sync, you
must first access your App Services App using your App ID. You can find your
App ID in the App Services UI.
<AppProvider id={APP_ID}>
<RestOfApp />
</AppProvider>
);
}
<UserProvider fallback={LogIn}>
<RestOfApp />
</UserProvider>
</AppProvider>
);
}
function LogIn() {
const app = useApp();
async function logInUser() {
// When anonymous authentication is enabled, users can immediately log
// into your app without providing any identifying information.
await app.logIn(Realm.Credentials.anonymous());
}
return (
<Button
title='Log In'
onPress={logInUser}
/>
);
}
After you have initialized your App, authenticated a user, and defined your
object model, you can configure a synced realm. This is similar to configuring
a local realm. However, you need to add some additional props to
the RealmProvider.
1. Create the realm's Configuration object. The Configuration object
defines the parameters of a realm and identifies it. When creating a
configuration object, make sure to pass your data models into
the schema property.
3. Extract the RealmProvider from the realm context, and then expose it
to your app.
You need at least one sync subscription before you can read or write synced
data. You can add subscriptions in your components or set up initial
subscriptions on RealmProvider.
TypeScript
JavaScript
import React from 'react';
import Realm from 'realm';
import {AppProvider, UserProvider, createRealmContext} from '@realm/react';
// Define your object model
class Profile extends Realm.Object {
static schema = {
name: 'Profile',
properties: {
_id: 'objectId',
name: 'string',
},
primaryKey: '_id',
};
}
// Create a configuration object
const realmConfig = {
schema: [Profile],
};
// Create a realm context
const {RealmProvider, useRealm, useObject, useQuery} =
createRealmContext(realmConfig);
// Expose a sync realm
function AppWrapperSync() {
return (
<AppProvider id={APP_ID}>
<UserProvider fallback={LogIn}>
<RealmProvider
sync={{
flexible: true,
onError: console.error,
initialSubscriptions: {
update(subs, realm) {
subs.add(realm.objects('Profile'));
},
},
}}>
<RestOfApp />
</RealmProvider>
</UserProvider>
</AppProvider>
);
}
The syntax to create, read, update, and delete objects in a synced realm is
identical to the syntax for non-synced realms. While you work with local data,
a background thread efficiently integrates, uploads, and downloads
changesets.
If you are interested in a guided experience, you can read our Realm React
Native SDK tutorial. This tutorial implements and expands on a base React
Native app built with Realm and Device Sync.
You could also use the template app to experiment with the React Native SDK
on your own. To set up the template app, refer to Template Apps in the Atlas
App Services documentation.
Realms are the core data structure used to organize data in Realm Database.
At its core, a realm is a collection of the objects that you use in your
application, called Realm objects, as well as additional metadata that describe
the objects.
Realm Files
Realm stores a binary encoded version of every object and type in a realm in a
single .realm file. The file is located at a specific path that you define when
you open the realm.
TIP
NOTE
WARNING
If you delete a realm file or any of its auxiliary files while one or more
instances of the realm are open, you might corrupt the realm or disrupt sync.
You may safely delete these files when all instances of a realm are closed.
Before you delete a realm file, make sure that you back up any important
objects as you will lose all unsynced data in the realm.
In-Memory Realms
You can also open a realm entirely in memory, which will not create
a .realm file or its associated auxiliary files. Instead the SDK stores objects in
memory while the realm is open and discards them immediately when all
instances are closed.
The realm file is located at a specific path that you can optionally define when
you open the realm.
// Open a realm.
const realm = await Realm.open({
schema: [Car],
});
// Get on-disk location of the Realm
const realmFileLocation = realm.path;
console.log(`Realm file is located at: ${realm.path}`);
The Realm React Native SDK and @realm/react package provide many
configuration options for your realm.
How you configure your realm determines the capabilities of your realm and
how you work with your data. This page contains information about how to
configure your realm in various ways.
Prerequisites
When RealmProvider is rendered, it opens the realm. This means that the
provider renders successfully or its child components can't access the realm.
Configuration Options
You can configure RealmProvider by setting props that match the properties
of a Configuration object. You can also set fallback and realmRef props.
• realmRef
Used with useRef to expose the configured realm to processes outside
of RealmProvider. This can be useful for things like a client reset fallback.
• fallback
Rendered while waiting for the realm to open. Local realms usually open fast
enough that the fallback prop isn't needed.
To create a realm that runs entirely in memory without being written to a file,
pass true to the inMemory prop on your RealmProvider:
Encrypt a Realm
To open a realm that synchronizes data with Atlas using Device Sync, refer
to Open a Synced Realm.
You can open more than one realm at a time by creating additional Context
objects using createRealmContext().
TypeScript
JavaScript
import React from 'react';
import {
Realm,
AppProvider,
UserProvider,
createRealmContext,
} from '@realm/react';
class SharedDocument extends Realm.Object {
static schema = {
name: 'SharedDocument',
properties: {
_id: 'objectId',
owner_id: 'objectId',
title: 'string',
createdDate: 'date',
},
primaryKey: '_id',
};
}
class LocalDocument extends Realm.Object {
static schema = {
name: 'LocalDocument',
properties: {
_id: 'objectId',
name: 'string',
createdDate: 'date',
},
};
}
// Create Shared Document context object.
const SharedRealmContext = createRealmContext({
schema: [SharedDocument],
});
// Create Local Document context object.
const LocalRealmContext = createRealmContext({
schema: [LocalDocument],
});
You need to extract providers and hooks from each Context object. You
should namespace the providers and hooks using destructuring. This makes it
easier to reason about the realm you're working with.
After extracting a Context object's providers and hooks, you can use them in
your app's components. Child components inside of extracted providers have
access to extracted hooks.
function TwoRealmsWrapper() {
return (
<View>
<AppProvider id={APP_ID}>
<UserProvider fallback={LogIn}>
{/* This is a Flexible Sync realm. */}
<SharedDocumentRealmProvider sync={{flexible: true}}>
<AppSectionOne />
</SharedDocumentRealmProvider>
</UserProvider>
</AppProvider>
{/* This is a separate local-only realm. */}
<LocalDocumentRealmProvider>
<AppSectionTwo />
</LocalDocumentRealmProvider>
</View>
);
}
function AppSectionOne() {
const realm = useSharedDocumentRealm();
// Work with shared documents...
}
function AppSectionTwo() {
const realm = useLocalDocumentRealm();
// Work with local documents...
}
Accessing a realm without providing a schema only works for local realms.
You must always pass a schema when using a Synced realm.
@realm/react has providers and hooks that simplify working with your non-
sync realm and its data.
RealmProviderA wrapper that exposes a realm to its child See Configure a Realm Without
components, which have access to hooks
that let you read, write, and update data.
useRealm Returns the instance of the Realm opened const realm = useRealm();
by the RealmProvider.
useObject Returns an object (Realm.Object<T>) from const myTask = useObject(Task,
a given type and value of primary key.
Updates on any changes to the returned
object. Returns null if the object either
doesn't exists or has been deleted.
useQuery Returns a collection of objects const tasks = useQuery(Task);
(Realm.Results<T & Realm.Object T>)
from a given type. Updates on any changes
Provider/Hook Description Example
Realm Files
Realm stores a binary encoded version of every object and type in a realm in a
single .realm file. The file is located at a specific path that you define when
you open the realm.
TIP
NOTE
Realm creates additional files for each realm. To learn more about these files,
see Realm Internals.
WARNING
If you delete a realm file or any of its auxiliary files while one or more
instances of the realm are open, you might corrupt the realm or disrupt sync.
You may safely delete these files when all instances of a realm are closed.
Before you delete a realm file, make sure that you back up any important
objects as you will lose all unsynced data in the realm.
Bundle a Realm File - React Native SDK
Realm supports bundling realm files. When you bundle a realm file, you
include a database and all of its data in your application download.
This allows users to start applications for the first time with a set of initial data.
For synced realms, bundling can avoid a lengthy initial download the first time
a user opens your application. Instead, users must only download the synced
changes that occurred since you generated the bundled file.
IMPORTANT
WARNING
This procedure doesn't work for React Native apps created with Expo.
Procedure
Follow these steps to create and bundle a realm file for your React Native
application.
The easiest way to create a bundled realm for your React Native app is to write
a separate Node.js script to create the bundle.
You should use the realm package to create your bundle rather
than @realm/react.
1. Build a temporary realm app that shares the data model of your
application.
2. Open a realm and add the data you wish to bundle. If using a
synchronized realm, allow time for the realm to fully sync.
create-bundled-realm.js
3. Note the filepath of the bundled realm file. You'll need this file to use the
bundled realm in your production application, as described in the next
section.
temp_realm_app
.
├── bundle.realm
... rest of files in _temp_ application
2
Now that you have a copy of the realm that contains the initial data, add the
bundled realm file to your production application. Where you place the
bundled realm differs for iOS and Android builds.
Android
iOS
1. Open up the android folder generated by React Native in Android
Studio.
2. In the Project tree, navigate to app > src > main. Right click
the main directory. Create a new subdirectory named assets.
The realm is now bundled and will be included when a user downloads the
app. To add the bundled realm file to your app's document directory,
call Realm.copyBundledRealmFiles() before you open the realm.
Open the bundled realm with the same name and configuration that you
specified when you initially created the bundled realm.
Now that you have a copy of the realm included with your production
application, you need to add code to use it.
You can encrypt the realm database file on disk with AES-256 + SHA-2 by
supplying a 64-byte encryption key when opening a realm.
WARNING
Considerations
You must pass the same encryption key every time you open the encrypted
realm. If you don't provide a key or specify the wrong key for an encrypted
realm, the Realm SDK throws an error.
Apps should store the encryption key securely, typically in the target
platform's secure key/value storage, so that other apps cannot read the key.
Performance Impact
Realm only encrypts the data on the device and stores the data unencrypted in
your Atlas data source. Any users with authorized access to the Atlas data
source can read the data, but the following still applies:
• Users must have the correct read permissions to read the synced data.
You can also enable Customer Key Management to encrypt stored Atlas data
using your cloud provider's key (e.g. AWS KMS, Azure Key Vault, Google
Cloud KMS).
If you need unique keys for each user of your application, you can use an
OAuth provider or use one of the Realm authentication providers and
an authentication trigger to create a 64-bit key and store that key in a user
object.
Starting with Realm React Native SDK version 11.8.0, Realm supports opening
the same encrypted realm in multiple processes.
If your app uses Realm React Native SDK version 11.7.0 or earlier, attempting
to open an encrypted realm from multiple processes throws this
error: Encrypted interprocess sharing is currently unsupported.
Example
The following code demonstrates how to generate an encryption key and open
an encrypted realm:
Over time, the storage space used by Realm might become fragmented and
take up more space than necessary. To rearrange the internal storage and
potentially reduce the file size, the realm file needs to be compacted.
Realm's default behavior is to automatically compact a realm file to prevent it
from growing too large. You can use manual compaction strategies when
automatic compaction is not sufficient for your use case.
Automatic Compaction
Automatic compaction begins when the size of unused space in the file is
more than twice the size of user data in the file. Automatic compaction only
takes place when the file is not being accessed.
Realm reduces the file size by writing a new (compact) version of the file and
then replacing the original with the newly-written file. Therefore, to compact,
you must have free storage space equivalent to the original realm file size.
You can configure Realm to automatically compact the database each time a
realm is opened, or you can compact the file without first obtaining a realm
instance.
Realm.compact() Method
Alternatively, you can compact a realm file without first obtaining an instance
of the realm by calling the compact() method:
IMPORTANT
These recommendations can help you start optimizing compaction for your
application:
• Set the max file size to a multiple of your average realm state size. If
your average realm state size is 10MB, you might set the max file size to
20MB or 40MB, depending on expected usage and device constraints.
• As a starting point, compact realms when more than 50% of the realm
file size is no longer in use. Divide the currently used bytes by the total
file size to determine the percentage of space that is currently used.
Then, check for that to be less than 50%. This means that greater than
50% of your realm file size is unused space, and it is a good time to
compact. After experimentation, you may find a different percentage
works best for your application.
Experiment with conditions to find the right balance of how often to compact
realm files in your application.
Realm guarantees that all objects in a realm conform to the schema for their
object type and validates objects whenever they're created, modified, or
deleted.
The React Native SDK memory maps Realm objects directly to native
JavaScript objects, which means there's no need to use a special data access
library, such as an ORM. Instead, you can work with Realm objects as you
would any other object.
Realm Schema
A realm schema is a list of valid object schemas that a realm may contain.
Every Realm object must conform to an object type that's included in its
realm's schema.
If a realm already contains data when you open it, Realm validates each object
to ensure that an object schema was provided for its type and that it meets all
of the constraints specified in the schema.
• One-to-One Relationship
• One-to-Many Relationship
• Inverse Relationship
NOTE
Objects often contain direct references to other objects. When working with
objects and references, you typically copy data from database storage into
application memory. This situation leaves the developer with a choice of what
to copy into memory:
• You can copy all referenced objects into memory ahead of time. This
means that all referenced data is always available quickly but can use
up a lot of resources. If a system has limited memory, this may not be
viable.
• You can copy just a foreign key value for each object. Later, you can use
the key to look up the full object when you need it. These "lazy" lookups
are more memory-efficient than copying all referenced objects ahead of
time. However, they require you to maintain more query code and use
runtime lookups that can slow your app down.
Realm's query architecture avoids the tradeoff between memory usage and
computational overhead. Instead, Realm queries can directly reference related
objects and their properties on disk.
TIP
See also:
Alternatively, you can define your relationships in your App Services app.
TypeScript
JavaScript
class Book extends Realm.Object {
static schema = {
name: 'Book',
properties: {
name: {type: 'string', indexed: true},
price: 'int?',
},
};
}
Then you can pass the class itself to the schema property of
the Configuration object when opening a realm.
Supported Property Types
Every property in a Realm object has a strongly defined data type. A property's
type can be a primitive data type or an object type defined in the same realm.
The type also specifies whether the property contains a single value or a list of
values.
To specify that a field contains a list of a primitive value type, append [] to the
type name.
To mark a property as optional, use object syntax and set optional to true.
You can also use a simplified syntax: append a question mark ? to the type.
This is best-suited to basic types. You should use the more specific object
syntax for more complicated types.
In the following example of a Person class, the age and birthday properties
are both optional.
NOTE
If an object type has a primary key, then all objects of that type must include
the primary key property with a unique value among objects of the same type
in a realm. An object type can have only one primary key. You cannot change
the primary key property for an object type after any object of that type is
added to a realm, and you cannot modify an object's primary key value.
In the following example of a Task class, we specify the _id property as the
primary key.
TypeScript
JavaScript
1 class Task extends Realm.Object {
2 static schema = {
3 name: 'Task',
4 properties: {
5 _id: 'int',
6 name: 'string',
7 priority: 'int?',
8 progressMinutes: 'int?',
9 assignee: 'Person?',
10 },
11 primaryKey: '_id',
12 };
13 }
Index a Property
If you frequently run read operations based on a specific property, you can
index the property to optimize performance. Realm supports indexing for
string, integer, boolean, Date, UUID, and ObjectId properties.
NOTE
TypeScript
JavaScript
1 class Book extends Realm.Object {
2 static schema = {
3 name: 'Book',
4 properties: {
5 name: {type: 'string', indexed: true},
6 price: 'int?',
7 },
8 };
9 }
For more information on querying FTS indexes, see Filter with Full-Text
Search.
To create an FTS index, set the indexed type to 'full-text'. This enables
full-text queries on the property. In the following example, we set the indexed
type for the name property to 'full-text':
To define a default value, set the value of the property to an object with
a type field and a default field.
In Realm.js v11.1.0 and later, you can use a function to define a dynamic
default value, like the timestamp property in the example below.
TypeScript
JavaScript
1 class Car extends Realm.Object {
2 static schema = {
3 name: 'Car',
4 properties: {
5 make: 'string',
6 model: 'string',
7 miles: {type: 'int', default: 0},
8 timestamp: {
9 type: 'int',
10 default: () => Math.round(new Date().getTime() / 1000),
11 },
12 },
13 };
14 }
Remap a Property
TypeScript
JavaScript
1 class Employee extends Realm.Object {
2 static schema = {
3 name: 'Employee',
4 properties: {
5 _id: 'string',
6 first_name: {type: 'string', mapTo: 'firstName'},
7 },
8 primaryKey: '_id',
9 };
10 }
If you are using Flexible Sync and need to sync a collection unidirectionally
from your decive to your Atlas database, you can set
the asymmetric property on your object schema.
TypeScript
JavaScript
class WeatherSensor extends Realm.Object{
static schema = {
name: 'WeatherSensor',
// sync WeatherSensor objects one way from your device
// to your Atlas database.
asymmetric: true,
primaryKey: '_id',
properties: {
_id: 'objectId',
deviceId: 'string',
temperatureInFahrenheit: 'int',
barometricPressureInHg: 'float',
windSpeedInMph: 'float',
},
};
}
NOTE
We recommend creating Realm objects with Realm.create(), but you can also
use the new operator for your object model's class.
If you use new, you must add your class as a generic, along with any required
properties, when extending Realm.Object. This enables full TypeScript
support for your object model, including type errors when required fields are
not defined.
One-to-One Relationship
EXAMPLE
TypeScript
JavaScript
1 class PetOwner extends Realm.Object {
2 static schema = {
3 name: 'PetOwner',
4 properties: {
5 name: 'string',
6 birthdate: 'date',
7 pet: 'Pet?',
8 },
9 };
10 }
1 class Pet extends Realm.Object {
2 static schema = {
3 name: 'Pet',
4 properties: {
5 name: 'string',
6 age: 'int',
7 animalType: 'string?',
8 },
9 };
10 }
One-to-Many Relationship
A one-to-many relationship means an object may be related to multiple
objects. To define a to-many relationship, specify a property where the type is
a list or array of the related Realm object type in its object schema.
EXAMPLE
TypeScript
JavaScript
1 class User extends Realm.Object {
2 static schema = {
3 name: 'User',
4 properties: {
5 _id: 'objectId',
6 name: 'string',
7 birthdate: 'date?',
8 posts: 'Post[]',
9 },
10 primaryKey: '_id',
11 };
12 }
1 class Post extends Realm.Object {
2 static schema = {
3 name: 'Post',
4 properties: {
5 _id: 'objectId',
6 title: 'string',
7 },
8 primaryKey: '_id',
9 };
10 }
Inverse Relationship
You can use inverse relationships to "backlink" from one object to another
based on a particular relationship. For example, if you define a relationship
that maps User.posts to a list of Post objects, then you could use an inverse
relationship to look up the User object related to a given Post object in this
way.
EXAMPLE
TypeScript
JavaScript
1 class User extends Realm.Object {
2 static schema = {
3 name: 'User',
4 properties: {
5 _id: 'objectId',
6 name: 'string',
7 birthdate: 'date?',
8 posts: 'Post[]',
9 },
10 primaryKey: '_id',
11 };
12 }
1 class Post extends Realm.Object {
2 static schema = {
3 name: 'Post',
4 properties: {
5 _id: 'objectId',
6 title: 'string',
7 user: {
8 type: 'linkingObjects',
9 objectType: 'User',
10 property: 'posts',
11 },
12 },
13 primaryKey: '_id',
14 };
15 }
You can find all objects that link to a given object by calling the
object's Realm.Object.linkingObjects() method. This is useful for when you
want to access all linking objects for a particular relationship without adding a
property to the object schema.
EXAMPLE
TypeScript
JavaScript
1 const PostItem = ({_id}) => {
2 const post = useObject(Post, _id);
3 const user = post?.linkingObjects('User', 'posts')[0];
4
5 if (!post || !user)
6 return <Text>The post or user could not be found</Text>;
7 return (
8 <View>
9 <Text>Post title: {post.title}</Text>
10 <Text>Post created by: {user.name}</Text>
11 </View>
12 );
13 };
EXAMPLE
A filter can match a Post object based on properties of the User object that
references it. In the following example, the @links operator references the
relationship defined for User.posts. If a User was born on or after January 1,
2000, then their Post objects are included in the query results.
TypeScript
JavaScript
1 const PostsByYoungUsers = () => {
2 const posts = useQuery(Post);
3 const postsByYoungUsers = useMemo(() => {
4 return posts.filtered(
5 '@links.User.posts.birthdate >= 2000-01-01@00:00:00:0',
6 );
7 }, [posts]);
8
9 if (!posts) return <Text>The post was not found.</Text>;
10 return (
11 <View>
12 <Text>Posts By Young Users</Text>
13 {postsByYoungUsers.map((post) => (
14 <Text key={post._id.toHexString()}>
15 {post.title}
16 </Text>
17 ))}
18 </View>
19 );
20 };
Embedded Objects
TIP
You can use the same embedded object type in multiple parent object types,
and you can embed objects inside other embedded objects. You can even
recursively reference an embedded object type as an optional property in its
own definition.
NOTE
When you delete a Realm object, Realm automatically deletes any embedded
objects referenced by that object. Any objects that your application must
persist after the deletion of their parent object should
use relationships instead.
TypeScript
JavaScript
1 class Address extends Realm.Object {
2 static schema = {
3 name: "Address",
4 embedded: true, // default: false
5 properties: {
6 street: "string?",
7 city: "string?",
8 country: "string?",
9 postalCode: "string?",
10 },
11 };
12 }
1 class Contact extends Realm.Object {
2 static schema = {
3 name: "Contact",
4 primaryKey: "_id",
5 properties: {
6 _id: "objectId",
7 name: "string",
8 // Embed a single object
9 address: "Address",
10 },
11 };
12 }
1 class Business extends Realm.Object {
2 static schema = {
3 name: "Business",
4 primaryKey: "_id",
5 properties: {
6 _id: "objectId",
7 name: "string",
8 // Embed an array of objects
9 addresses: { type: "list", objectType: "Address" },
10 },
11 };
12 }
IMPORTANT
JSON Schema
{
"title": "Contact",
"bsonType": "object",
"required": ["_id"],
"properties": {
"_id": { "bsonType": "objectId" },
"name": { "bsonType": "string" },
"address": {
"title": "Address",
"bsonType": "object",
"properties": {
"street": { "bsonType": "string" },
"city": { "bsonType": "string" },
"country": { "bsonType": "string" },
"postalCode": { "bsonType": "string" }
}
}
}
}
{
"title": "Business",
"bsonType": "object",
"required": ["_id", "name"],
"properties": {
"_id": { "bsonType": "objectId" },
"name": { "bsonType": "string" },
"addresses": {
"bsonType": "array",
"items": {
"title": "Address",
"bsonType": "object",
"properties": {
"street": { "bsonType": "string" },
"city": { "bsonType": "string" },
"country": { "bsonType": "string" },
"postalCode": { "bsonType": "string" }
}
}
}
}
}
NOTE
When updating your object schema, you must increment the schema version
and perform a migration.
For more complex schema updates, you must also manually specify the
migration logic in a migration function. This might include changes such as:
• Combining fields
• Renaming a field
TIP
When developing or debugging your application, you may prefer to delete the
realm instead of migrating it. Use
the BaseConfiguration.deleteRealmIfMigrationNeeded property to delete the
database automatically when a schema mismatch requires a migration.
Schema Version
A schema version identifies the state of a realm schema at some point in time.
Realm tracks the schema version of each realm and uses it to map the objects
in each realm to the correct schema.
Schema versions are ascending integers you can include in the realm
configuration. If a client application does not specify a version number, the
realm defaults to version 0.
IMPORTANT
Migrations
A migration is a function that updates a realm and any objects it contains from
one schema version to a newer version. Migrations allow you to change your
object schemas over time to accommodate new features and refactors.
When you create a Configuration with a schema version greater than the
realm's current version, Realm runs a migration function that you define. The
function has access to the realm's version number and incrementally updates
objects in the realm to conform to the new schema.
Add a Property
To add a property to a schema, add the new property to the object's class and
set a schemaVersion of the Configuration object.
EXAMPLE
A realm using schema version 0, the default, has a Person object type with
a firstName and lastName property. You decide to add an age property to
the Person class.
To migrate the realm to conform to the updated Person schema, you set the
realm's schema version in the Configuration to 1. Finally, pass the
configuration object to the createRealmContext() method.
TypeScript
JavaScript
class Person extends Realm.Object {
static schema = {
name: 'Person',
properties: {
_id: 'string',
firstName: 'string',
lastName: 'string',
// add a new property, 'age' to the schema
age: 'int',
},
};
}
const config = {
schema: [Person],
// Increment the 'schemaVersion', since 'age' has been added to the schema.
// The initial schemaVersion is 0.
schemaVersion: 2,
};
// pass the configuration object with the updated 'schemaVersion' to
// createRealmContext()
const {RealmProvider} = createRealmContext(config);
Delete a Property
To delete a property from a schema, remove the property from the object's
class and set a schemaVersion of the Configuration object. Deleting a
property will not impact existing objects.
EXAMPLE
A realm using schema version 0, the default, has a Person object type with
a lastName property. You decide to remove the property from the schema.
To migrate the realm to conform to the updated Person schema, set the
realm's schema version to 1 in the Configuration object. Finally, pass the
configuration object to the createRealmContext() method.
TypeScript
JavaScript
class Person extends Realm.Object {
static schema = {
name: 'Person',
properties: {
_id: 'string',
firstName: 'string',
age: 'int',
},
};
}
const config = {
schema: [Person],
// Increment the 'schemaVersion', since 'lastName' has been removed from the schema.
// The initial schemaVersion is 0.
schemaVersion: 1,
};
// pass the configuration object with the updated 'schemaVersion' to createRealmContext()
const {RealmProvider} = createRealmContext(config);
Rename a Property
To rename an object property, change the property name in the object schema
and then create a realm configuration with an incremented schema
version and a migration function that updates existing objects to use the new
property name.
Migrations do not allow you to directly rename a property. Instead, you can
create a new property with the updated name, copy the value from the old
property, and then delete the old property.
EXAMPLE
A realm using schema version 0, the default, has a Person object type. The
original schema had a firstName and lastName field. You later decide that
the Person class should use a combined fullName field and removes the
separate firstName and lastName fields.
TypeScript
JavaScript
class Person extends Realm.Object {
static schema = {
name: 'Person',
properties: {
_id: 'string',
// rename the 'firstName' and 'lastName' property, to 'fullName'
// in the schema
fullName: 'string',
age: 'int',
},
};
}
const config = {
schema: [Person],
// Increment the 'schemaVersion', since 'fullName' has replaced
// 'firstName' and 'lastName' in the schema.
// The initial schemaVersion is 0.
schemaVersion: 1,
onMigration: (oldRealm, newRealm) => {
// only apply this change if upgrading schemaVersion
if (oldRealm.schemaVersion < 1) {
const oldObjects =
oldRealm.objects(Person);
const newObjects = newRealm.objects(Person);
// loop through all objects and set the fullName property in the
// new schema
for (const objectIndex in oldObjects) {
const oldObject = oldObjects[objectIndex];
const newObject = newObjects[objectIndex];
newObject.fullName = `${oldObject.firstName} ${oldObject.lastName}`;
}
}
},
};
// pass the configuration object with the updated 'schemaVersion' and
// 'migration' function to createRealmContext()
const {RealmProvider} = createRealmContext(config);
IMPORTANT
Synced Realms
To modify a property's type, set the property type of the field that you wish to
modify to the new data type. Then, set a schemaVersion, and
a migration callback function of the Configuration object.
NOTE
EXAMPLE
A realm using schema version 0, the default, has a Person object type. The
original schema had an _id with a property type of int. You later decide that
the Person class's _id field should be of type ObjectId, and updates the
schema.
TypeScript
JavaScript
class Person extends Realm.Object {
static schema = {
name: 'Person',
properties: {
// update the data type of '_id' to be 'objectId' within the schema
_id: 'objectId',
firstName: 'string',
lastName: 'string',
},
};
}
const config = {
schema: [Person],
// Increment the 'schemaVersion', since the property type of '_id'
// has been modified.
// The initial schemaVersion is 0.
schemaVersion: 1,
onMigration: (oldRealm, newRealm) => {
if (oldRealm.schemaVersion < 1) {
const oldObjects =
oldRealm.objects(Person);
const newObjects = newRealm.objects(Person);
// loop through all objects and set the _id property
// in the new schema
for (const objectIndex in oldObjects) {
const oldObject = oldObjects[objectIndex];
const newObject = newObjects[objectIndex];
newObject._id = new Realm.BSON.ObjectId(oldObject._id);
}
}
},
};
// Pass the configuration object with the updated
// 'schemaVersion' and 'migration' function to createRealmContext()
const {RealmProvider} = createRealmContext(config);
NOTE
When updating your object schema, you must increment the schema version
and perform a migration.
For more complex schema updates, you must also manually specify the
migration logic in a migration function. This might include changes such as:
• Combining fields
• Renaming a field
TIP
When developing or debugging your application, you may prefer to delete the
realm instead of migrating it. Use
the BaseConfiguration.deleteRealmIfMigrationNeeded property to delete the
database automatically when a schema mismatch requires a migration.
A schema version identifies the state of a realm schema at some point in time.
Realm tracks the schema version of each realm and uses it to map the objects
in each realm to the correct schema.
Schema versions are ascending integers you can include in the realm
configuration. If a client application does not specify a version number, the
realm defaults to version 0.
IMPORTANT
Migrations
A migration is a function that updates a realm and any objects it contains from
one schema version to a newer version. Migrations allow you to change your
object schemas over time to accommodate new features and refactors.
When you create a Configuration with a schema version greater than the
realm's current version, Realm runs a migration function that you define. The
function has access to the realm's version number and incrementally updates
objects in the realm to conform to the new schema.
To add a property to a schema, add the new property to the object's class and
set a schemaVersion of the Configuration object.
EXAMPLE
A realm using schema version 0, the default, has a Person object type with
a firstName and lastName property. You decide to add an age property to
the Person class.
To migrate the realm to conform to the updated Person schema, you set the
realm's schema version in the Configuration to 1. Finally, pass the
configuration object to the createRealmContext() method.
TypeScript
JavaScript
class Person extends Realm.Object {
static schema = {
name: 'Person',
properties: {
_id: 'string',
firstName: 'string',
lastName: 'string',
// add a new property, 'age' to the schema
age: 'int',
},
};
}
const config = {
schema: [Person],
// Increment the 'schemaVersion', since 'age' has been added to the schema.
// The initial schemaVersion is 0.
schemaVersion: 2,
};
// pass the configuration object with the updated 'schemaVersion' to
// createRealmContext()
const {RealmProvider} = createRealmContext(config);
Delete a Property
To delete a property from a schema, remove the property from the object's
class and set a schemaVersion of the Configuration object. Deleting a
property will not impact existing objects.
EXAMPLE
A realm using schema version 0, the default, has a Person object type with
a lastName property. You decide to remove the property from the schema.
To migrate the realm to conform to the updated Person schema, set the
realm's schema version to 1 in the Configuration object. Finally, pass the
configuration object to the createRealmContext() method.
TypeScript
JavaScript
class Person extends Realm.Object {
static schema = {
name: 'Person',
properties: {
_id: 'string',
firstName: 'string',
age: 'int',
},
};
}
const config = {
schema: [Person],
// Increment the 'schemaVersion', since 'lastName' has been removed from the schema.
// The initial schemaVersion is 0.
schemaVersion: 1,
};
// pass the configuration object with the updated 'schemaVersion' to createRealmContext()
const {RealmProvider} = createRealmContext(config);
Rename a Property
To rename an object property, change the property name in the object schema
and then create a realm configuration with an incremented schema
version and a migration function that updates existing objects to use the new
property name.
Migrations do not allow you to directly rename a property. Instead, you can
create a new property with the updated name, copy the value from the old
property, and then delete the old property.
EXAMPLE
A realm using schema version 0, the default, has a Person object type. The
original schema had a firstName and lastName field. You later decide that
the Person class should use a combined fullName field and removes the
separate firstName and lastName fields.
TypeScript
JavaScript
class Person extends Realm.Object {
static schema = {
name: 'Person',
properties: {
_id: 'string',
// rename the 'firstName' and 'lastName' property, to 'fullName'
// in the schema
fullName: 'string',
age: 'int',
},
};
}
const config = {
schema: [Person],
// Increment the 'schemaVersion', since 'fullName' has replaced
// 'firstName' and 'lastName' in the schema.
// The initial schemaVersion is 0.
schemaVersion: 1,
onMigration: (oldRealm, newRealm) => {
// only apply this change if upgrading schemaVersion
if (oldRealm.schemaVersion < 1) {
const oldObjects =
oldRealm.objects(Person);
const newObjects = newRealm.objects(Person);
// loop through all objects and set the fullName property in the
// new schema
for (const objectIndex in oldObjects) {
const oldObject = oldObjects[objectIndex];
const newObject = newObjects[objectIndex];
newObject.fullName = `${oldObject.firstName} ${oldObject.lastName}`;
}
}
},
};
// pass the configuration object with the updated 'schemaVersion' and
// 'migration' function to createRealmContext()
const {RealmProvider} = createRealmContext(config);
IMPORTANT
Synced Realms
To modify a property's type, set the property type of the field that you wish to
modify to the new data type. Then, set a schemaVersion, and
a migration callback function of the Configuration object.
NOTE
EXAMPLE
A realm using schema version 0, the default, has a Person object type. The
original schema had an _id with a property type of int. You later decide that
the Person class's _id field should be of type ObjectId, and updates the
schema.
TypeScript
JavaScript
class Person extends Realm.Object {
static schema = {
name: 'Person',
properties: {
// update the data type of '_id' to be 'objectId' within the schema
_id: 'objectId',
firstName: 'string',
lastName: 'string',
},
};
}
const config = {
schema: [Person],
// Increment the 'schemaVersion', since the property type of '_id'
// has been modified.
// The initial schemaVersion is 0.
schemaVersion: 1,
onMigration: (oldRealm, newRealm) => {
if (oldRealm.schemaVersion < 1) {
const oldObjects =
oldRealm.objects(Person);
const newObjects = newRealm.objects(Person);
// loop through all objects and set the _id property
// in the new schema
for (const objectIndex in oldObjects) {
const oldObject = oldObjects[objectIndex];
const newObject = newObjects[objectIndex];
newObject._id = new Realm.BSON.ObjectId(oldObject._id);
}
}
},
};
// Pass the configuration object with the updated
// 'schemaVersion' and 'migration' function to createRealmContext()
const {RealmProvider} = createRealmContext(config);
• list maps to the JavaScript Array type. You can also specify that a
field contains a list of primitive value type by appending [] to the type
name.
To learn how specific data types are mapped to BSON types in an App
Services Schema, refer to Data Model Mapping in the Atlas App Services
documentation.
You can filter and sort any collection using Realm's query engine. Collections
are live, so they always reflect the current state of the realm instance on the
current thread. You can also listen for changes in the collection by
subscribing to collection notifications.
Results
TIP
See also:
Reads
Lists
A List represents a to-many relationship between two Realm types. Lists are
mutable: within a write transaction, you can add and remove elements to and
from a list. Lists are not associated with a query and are declared as a
property of an object model.
TIP
See also:
To-Many Relationships
Realm only runs a query when you request the results of that query. This lazy
evaluation enables you to write elegant, highly-performant code for handling
large data sets and complex queries.
• Live lists always reflect the current state of the relationship on the realm
instance.
IMPORTANT
As a result of lazy evaluation, you do not need any special mechanism to limit
query results with Realm. For example, if your query matches thousands of
objects, but you only want to load the first ten, access only the first ten
elements of the results collection.
Pagination
Summary
• There are two main kinds of collection: lists and results. Lists define
the to-many relationships of your Realm types, while results represent
the lazily-loaded output of a read operation.
• Data in Realm is live, which means that an object always reflects its
most recent saved state.
You can use the Realm.Dictionary data type to manage a collection of unique
String keys paired with values. The dictionary data maps to the
Javascript Object type.
For example, creating a HomeOwner Realm object where the home property is
defined as a dictionary type could look like this:
realm.create('HomeOwner', {
name: 'Anna Smith',
home: {address: '2 jefferson lane', yearRenovated: 1994, color: 'blue'},
});
Realm Object Models
You can define define a dictionary of mixed values for a Realm object model in
three ways:
• Add the data type before the brackets to create a dictionary with values
of a specific type. For example, "int{}" to specify that dictionary
values must be integers or "string{}" to specify that dictionary values
must be strings.
• Define the object type explicitly. This is necessary for using object
Types in your Realm as ditionary values.
TypeScript
JavaScript
class HomeOwner extends Realm.Object {
static schema = {
name: 'HomeOwner',
properties: {
name: 'string',
home: '{}',
pets: {
type: 'dictionary',
objectType: 'Pet',
optional: true
},
},
};
}
Realm disallows the use of . or $ characters in map keys. You can use
percent encoding and decoding to store a map key that contains one of these
disallowed characters.
You can also determine whether a results collection has a certain key or value
by using <dictionary>.@keys or <dictionary>.@values. For instance, if
you had a HomeOwner collection with a nested home dictionary, you could
return all HomeOwner objects with a home with a "price" property by running
the query: home.@keys = "price".
Example
In the following HomeList example, we query for objects that have dictionary
properties.
• Performs a query for all homeowners with any field with a value of red
by passing collection.filtered() the query: 'home.@values =
"red"'. We then get the first homeowner's home.
• Display the results of our queries in the UI by rendering information
about the homes
TypeScript
JavaScript
1 const HomeList = () => {
2 // query for all HomeOwner objects
3 const homeOwners = useQuery(HomeOwner);
4
5 // run the `.filtered()` method on all the returned homeOwners to
6 // find all homeOwners that have a house with a listed price
7 const listedPriceHomes = homeOwners.filtered('home.@keys = "price"');
8
9 // run the `.filtered()` method on all the returned homeOwners to
10 // find the house with the address "Summerhill St."
11 const summerHillHouse = homeOwners.filtered(
12 'home["address"] = "Summerhill St."',
13 )[0].home;
14
15 // run the `.filtered()` method on all the returned homeOwners to
16 // find the first house that has any field with a value of 'red'
17 const redHouse = homeOwners.filtered('home.@values = "red"')[0].home;
18 return (
19 <View>
20 <Text>All homes:</Text>
21 {homeOwners.map(homeOwner => (
22 <View>
23 <Text>{homeOwner.home.address}</Text>
24 </View>
25 ))}
26
27 <Text>All homes with a price:</Text>
28 {listedPriceHomes.map(homeOwner => (
29 <View>
30 <Text>{homeOwner.home.address}</Text>
31 <Text>{homeOwner.home.price}</Text>
32 </View>
33 ))}
34
35 <Text>Summer Hill House:</Text>
36 <Text>{summerHillHouse.address}</Text>
37 <Text>{summerHillHouse.color}</Text>
38
39 <Text>Red House:</Text>
40 <Text>{redHouse.address}</Text>
41 </View>
42 );
43 };
Update a Dictionary
Example
Example
• Retrieve the first homeowner that matches the name passed into the
component as a prop. We do this by getting the first value returned from
the query: useQuery(HomeOwner).filtered(`name ==
'${homeOwnerName}'`).
• Add an onPress event on the "Delete extra home info" button that
calls deleteExtraHomeInfo().
TypeScript
JavaScript
1 const HomeInfo = ({homeOwnerName}) => {
2 const realm = useRealm();
3 const homeOwner = useQuery(HomeOwner).filtered(
4 `name == '${homeOwnerName}'`,
5 )[0];
6
7 const deleteExtraHomeInfo = () => {
8 realm.write(() => {
9 // remove the 'yearRenovated' and 'color' field of the house
10 homeOwner.home.remove(['yearRenovated', 'color']);
11 });
12 };
13
14 return (
15 <View>
16 <Text>{homeOwner.name}</Text>
17 <Text>{homeOwner.home.address}</Text>
18 <Button
19 onPress={deleteExtraHomeInfo}
20 title='Delete extra home info'
21
22 />
23 </View>
24 );
25 };
A Realm Set is a special object that allows you to store a collection of unique
values. Realm Sets are based on JavaScript sets, but can only contain values
of a single type and can only be modified within a write transaction. Sets allow
you to perform math operations such as finding the union, intersection, or
difference between two Sets. To learn more about performing these
operations, see the MDN docs for Implementing basic set operations.
You can define a Realm object model property type as a Realm Set, in a two
ways:
• Specify the data type the Set will contain, followed by <>.
• Use object notation and the type field for more complicated properties.
TypeScript
JavaScript
1 class Character extends Realm.Object {
2 static schema = {
3 name: 'Character',
4 primaryKey: '_id',
5 properties: {
6 _id: 'objectId',
7 name: 'string',
8 levelsCompleted: 'int<>',
9 inventory: {
10 type: 'set',
11 objectType: 'string',
12 }
13 },
14 };
15 }
To create an object with a Realm Set property, you must create the object
within a write transaction. When defining your Realm object, initialize the
Realm Set by passing an empty array or an array with your initial values.
Example
To add an item to a Set, pass the new value to the Realm.Set.add() method
method within a write transaction.
Example
Check if a Set has Specific Items and Check the Size of a Set
You may want to check for information about your Set, such as its size or if it
contains specific item.
To discover how many items are in a Set, you can check its size property.
Example
• Uses the useQuery hook to perform a query for all characters, and filter
the results to only include the characters with the name matching
the characterName passed to the component as a prop. Then we get
the first matching result.
• Renders the character's name, and renders the inventory size using
the size property of the character's inventory. It also renders
a TextInput that changes the inventoryItem state variable, and
a Button that calls the queryCharacterInventory method.
TypeScript
JavaScript
1 const QueryCharacterInventory = ({characterName}) => {
2 const [inventoryItem, setInventoryItem] = useState('');
3 const character = useQuery(Character).filtered(
4 `name = '${characterName}'`,
5 )[0];
6
7 const queryCharacterInventory = () => {
8 const characterDoesHaveItem = character.inventory.has(inventoryItem);
9 if (characterDoesHaveItem) {
10 Alert.alert(`Character has item: ${inventoryItem}`);
11 } else {
12 Alert.alert(`Item not found in character's inventory`);
13 }
14 };
15 return (
16 <View>
17 <Text>{character.name}</Text>
18 <Text>
19 Total number of inventory items: {character.inventory.size}
20 </Text>
21 <TextInput
22 onChangeText={text => setInventoryItem(text)}
23 value={inventoryItem}
24 />
25 <Button
26 title='Query for Inventory'
27 onPress={queryCharacterInventory}
28 />
29 </View>
30 );
31 };
You may want to remove a specific item or all items from a Set.
To clear the Set, run the Realm.Set.clear() method within a write transaction.
Example
Traverse a Set
You can traverse a Set to access each item in the Set. To traverse a Set, use
the Set.map() method or alternative iteration method.
Example
• Creates a state variable called "inventory" that will hold the character's
inventory items in order of insertion.
• Renders a list of the character's inventory items in the order they were
added to the Set by iterating through the inventory array state
variable.
The Mixed data type is a realm property type that can hold any valid Realm
data type except a collection. You can create collections (lists, sets, and
dictionaries) of type mixed, but a mixed type itself cannot be a collection.
The Mixed type is indexable, but you can't use it as a primary key.
Properties using the Mixed type can hold null values and cannot be defined as
optional. All instances of the JavaScript Number type in a Realm Mixed type
are mapped to the Realm double type.
To set a property of your object model as Mixed, set the property's type
to mixed.
TypeScript
JavaScript
class Cat extends Realm.Object {
static schema = {
name: 'Cat',
properties: {
name: 'string',
birthDate: 'mixed',
},
};
}
Create an object with a Mixed value by using the new operator within a write
transaction.
Example
To query for objects with a Mixed value, run the Collection.filtered() method
and pass in a filter for a non-Mixed field. You can then print the value of the
Mixed property or the entire object itself.
Example
In the following CatInfoCard example, we query for a Cat object using the
cat's name.
The CatInfoCard component does the following:
• Get all Cat objects by passing the Cat class to the useQuery() hook,
and then use filtered() to filter the results to receive only the cats
whose names match the name passed as a prop. We then get the first
matching cat and store it as a const variable.
• Display the cat's name and birthdate in the render method if Realm finds
the cat. If there is no cat that matches the name passed into the
component as a prop, we render text that says "Cat not found".
TypeScript
JavaScript
1 const CatInfoCard = ({catName}) => {
2 // To query for the cat's birthDate, filter for their name to retrieve the realm object.
3 // Use dot notation to access the birthDate property.
4 const cat = useQuery(Cat).filtered(`name = '${catName}'`)[0];
5 const catBirthDate = cat.birthDate;
6
7 if (cat) {
8 return (
9 <>
10 <Text>{catName}</Text>
11 <Text>{String(catBirthDate)}</Text>
12 </>
13 );
14 } else {
15 return <Text>Cat not found</Text>;
16 }
17 };
Because Mixed properties can be more than one type, you can't rely on the
property's value being a specific type.
You can use UUID as an unique identifier for objects. UUID is indexable, and
you can use it as a primary key.
TypeScript
JavaScript
class Profile extends Realm.Object {
static schema = {
name: 'Profile',
primaryKey: '_id',
properties: {
_id: 'uuid',
name: 'string',
},
};
}
Usage
To define a property as a UUID, set its type to "uuid" in your object model.
Create a Realm object within a write transaction. To set any unique identifier
properties of your object to a random value, call new UUID(). Alternatively,
pass a string to new UUID() to set the unique identifier property to a specific
value.
Example
• Renders a TextInput component that allows the user to enter a name for
the profile. When the user presses the "Create Profile" button,
the createProfile method is called and creates a Profile object.
TypeScript
JavaScript
1 const CreateProfileInput = () => {
2 const realm = useRealm();
3 const [name, setName] = useState('');
4
5 // createProfile creates a new 'Profile' Realm Object with a new UUID based on user input
6 const createProfile = () => {
7 realm.write(() => {
8 realm.create('Profile', {
9 name,
10 _id: new Realm.BSON.UUID(),
11 });
12 });
13 };
14 return (
15 <View>
16 <TextInput
17 placeholder='Name'
18 onChangeText={setName}
19 />
20 <Button
21 title='Create Profile'
22 onPress={createProfile}
23 />
24 </View>
25 );
Example
• Creates React state variables that represent the contact's name and
address details.
• Gets access to an open realm instance by calling the useRealm() hook
within the component.
You can use dot notation to filter or sort a collection of objects based on an
embedded object property value.
Example
Example
• Creates a React state variable that represents the contact's new street
address.
Example
In the following OverwriteContact example, we overwrite an
embedded Address object.
• Creates React state variables that represent the contact's new address.
Example
Within a RealmProvider, you can access a realm with the useRealm() hook.
Then, you can create Realm objects using a Realm.write() transaction block.
All operations within a write transaction are atomic. If an operation in the write
transaction fails, the whole transaction fails, Realm throws an error, and no
changes from the transaction block are applied to the realm.
Transaction Lifecycle
A given realm only processes one write transaction at a time. When you make
a write transaction, the realm adds the transaction to a queue. The realm
evaluates each transaction in the order it arrived.
• After a commit, the realm applies all operations in the transaction. Once
applied, the realm automatically updates live queries. It notifies listeners
of created, modified, and deleted objects.
o When using Sync, the SDK also queues the changes to send to
Atlas App Services. The SDK sends these changes when a
network is available.
TypeScript
JavaScript
class Person extends Realm.Object{
static schema = {
name: 'Person',
primaryKey: '_id',
properties: {
_id: 'objectId',
name: 'string',
age: 'int?',
},
};
}
TypeScript
JavaScript
class Pet extends Realm.Object {
static schema = {
name: 'Pet',
primaryKey: '_id',
properties: {
_id: 'objectId',
name: 'string',
age: 'int',
animalType: 'string?',
},
};
}
class PetOwner extends Realm.Object {
static schema = {
name: 'PetOwner',
primaryKey: '_id',
properties: {
_id: 'objectId',
name: 'string',
age: 'int',
pet: 'Pet?'
},
};
}
1. Query the realm for a pre-existing Pet object. Assign the result
to newPet.
2. Create a new PetOwner object and pass newPet to the pet property.
The example for creating an object with a to-many relationship uses the
following schema to indicate that a Company may employ multiple Employees:
TypeScript
JavaScript
class Employee extends Realm.Object {
static schema = {
name: 'Employee',
primaryKey: '_id',
properties: {
_id: 'objectId',
name: 'string',
birthdate: 'date',
},
};
}
class Company extends Realm.Object {
static schema = {
name: 'Company',
primaryKey: '_id',
properties: {
_id: 'objectId',
name: 'string',
employees: {
type: 'list',
objectType: 'Employee',
optional: false,
},
},
};
}
1. Query the realm for all pre-existing Employee objects using useQuery().
2. Create a new Company object and pass the results of your previous
query to the employees property.
The example for representing an embedded object uses the following schema
that allows you to embed a single Address into a new Contact object:
TypeScript
JavaScript
1 class Address extends Realm.Object {
2 static schema = {
3 name: "Address",
4 embedded: true, // default: false
5 properties: {
6 street: "string?",
7 city: "string?",
8 country: "string?",
9 postalCode: "string?",
10 },
11 };
12 }
1 class Contact extends Realm.Object {
2 static schema = {
3 name: "Contact",
4 primaryKey: "_id",
5 properties: {
6 _id: "objectId",
7 name: "string",
8 // Embed a single object
9 address: "Address",
10 },
11 };
12 }
1 class Business extends Realm.Object {
2 static schema = {
3 name: "Business",
4 primaryKey: "_id",
5 properties: {
6 _id: "objectId",
7 name: "string",
8 // Embed an array of objects
9 addresses: { type: "list", objectType: "Address" },
10 },
11 };
12 }
1. Creates React state variables that represent the contact's name and
address details.
The example for creating an asymmetric object uses the following schema that
defines a Weather Sensor object for sending weather-related data one-way
from your device to your Atlas database:
TypeScript
JavaScript
class WeatherSensor extends Realm.Object{
static schema = {
name: 'WeatherSensor',
// sync WeatherSensor objects one way from your device
// to your Atlas database.
asymmetric: true,
primaryKey: '_id',
properties: {
_id: 'objectId',
deviceId: 'string',
temperatureInFahrenheit: 'int',
barometricPressureInHg: 'float',
windSpeedInMph: 'float',
},
};
}
Read operations are queries to find your data stored in Realm. Use the
following @realm/react hooks to read data in a realm:
These hooks return live objects, which are automatically updated when the
data in the realm changes. When objects returned by these hooks are updated,
the component calling the hook rerenders.
TypeScript
JavaScript
class Person extends Realm.Object {
static schema = {
name: 'Person',
properties: {
name: 'string',
age: 'int?',
},
};
}
class Task extends Realm.Object {
static schema = {
name: 'Task',
properties: {
_id: 'int',
name: 'string',
priority: 'int?',
progressMinutes: 'int?',
assignee: 'Person?',
},
primaryKey: '_id',
};
}
If you know the primary key for a given object, you can look it up directly by
passing the class type and primary key to the useObject() hook.
In the following example of a TaskItem component, we use
the useObject() hook to find a task based on its primary key: _id. Then we
render the task's name and priority in the UI.
TypeScript
JavaScript
const TaskItem = ({_id}) => {
const myTask = useObject(Task, _id);
return (
<View>
{myTask ? (
<Text>
{myTask.name} is a task with the priority of: {myTask.priority}
</Text>
) : null}
</View>
);
};
The useQuery() hook returns a collection of Realm objects that match the
query as a Realm.Results object. A basic query matches all objects of a given
type in a realm, but you can also apply a filter to the collection to find specific
objects.
A filter selects a subset of results based on the value(s) of one or more object
properties. Realm lets you filter data using Realm Query Language, a string-
based query language to constrain searches when retrieving objects from a
realm.
Call filtered() on the query results collection to filter a query. Pass a Realm
Query Language query as an argument to filtered().
TIP
TIP
See also:
A sort operation allows you to configure the order in which Realm returns
queried objects. You can sort based on one or more properties of the objects
in the results collection. Realm only guarantees a consistent order of results if
you explicitly sort them.
To sort a query, call the sorted() method on the query results collection.
3. Sort objects based on the task's priority in descending order and the
task's name in ascending order.
Finally, we map through each list of tasks and render them in the UI.
TypeScript
JavaScript
const TaskList = () => {
// retrieve the set of Task objects
const tasks = useQuery(Task);
// Sort tasks by name in ascending order
const tasksByName = tasks.sorted('name');
// Sort tasks by name in descending order
const tasksByNameDescending = tasks.sorted('name', true);
// Sort tasks by priority in descending order and then by name alphabetically
const tasksByPriorityDescendingAndName = tasks.sorted([
['priority', true],
['name', false],
]);
// Sort Tasks by Assignee's name.
const tasksByAssigneeName = tasks.sorted('assignee.name');
return (
<>
<Text>All tasks:</Text>
{tasks.map(task => (
<Text>{task.name}</Text>
))}
<Text>Tasks sorted by name:</Text>
{tasksByName.map(task => (
<Text>{task.name}</Text>
))}
<Text>Tasks sorted by name descending:</Text>
{tasksByNameDescending.map(task => (
<Text>{task.name}</Text>
))}
<Text>
Tasks sorted by priority descending, and name alphabetically:
</Text>
{tasksByPriorityDescendingAndName.map(task => (
<Text>
{task.name}
</Text>
))}
<Text>Tasks sorted by assignee name:</Text>
{tasksByAssigneeName.map(task => (
<Text>{task.name}</Text>
))}
</>
);
};
TIP
TypeScript
JavaScript
class Task extends Realm.Object {
static schema = {
name: 'Task',
properties: {
_id: 'int',
name: 'string',
priority: 'int?',
progressMinutes: 'int?',
assignee: 'Person?',
},
primaryKey: '_id',
};
}
Update an Object
You can add, modify, or delete properties of a Realm object in the same way
that you would update any other JavaScript object. But, you must do it inside
of a write transaction.
TIP
Upsert an Object
NOTE
2. Perform a write transaction, and create a Task object with an _id value
of 1234.
4. Render the task's name and progressMinutes in the UI, showing the
modified progress.
TypeScript
JavaScript
const CreateTaskItem = () => {
const realm = useRealm();
let myTask;
realm.write(() => {
// Add a new Task to the realm. Since no task with ID 1234
// has been added yet, this adds the instance to the realm.
myTask = realm.create(
'Task',
{_id: 1234, name: 'Wash the car', progressMinutes: 0},
'modified',
);
// If an object exists, setting the third parameter (`updateMode`) to
// "modified" only updates properties that have changed, resulting in
// faster operations.
myTask = realm.create(
'Task',
{_id: 1234, name: 'Wash the car', progressMinutes: 5},
'modified',
);
});
return (
<>
<Text>{myTask.name}</Text>
<Text>Progress made (in minutes):</Text>
<Text>{myTask.progressMinutes}</Text>
</>
);
};
TypeScript
JavaScript
class Dog extends Realm.Object {
static schema = {
name: 'Dog',
properties: {
name: 'string',
owners: {
type: 'list',
objectType: 'Person',
optional: true,
},
age: 'int?',
},
};
}
Delete an Object
4. Map through the dogs to render a list of Text components that contain
a dog's name and a "Delete Dog" button.
5. Add an onPress event on the "Delete Dog" button that calls the
component's deleteDog() method.
TypeScript
JavaScript
const DogList = () => {
const realm = useRealm();
const myDogs = useQuery(Dog);
const deleteDog = deletableDog => {
realm.write(() => {
realm.delete(deletableDog);
});
};
return (
<>
{myDogs.map(dog => {
return (
<>
<Text>{dog.name}</Text>
<Button
onPress={() => deleteDog(dog)}
title='Delete Dog'
/>
</>
);
})}
</>
);
};
IMPORTANT
You cannot access or modify an object after you have deleted it from a Realm.
If you try to use a deleted object, Realm throws an error.
1. To delete all objects of a given object type from a realm, pass the results
of useQuery(<ObjectType>) to the Realm.delete() method inside of a
write transaction.
2. To delete many specific objects from a realm,
pass Collection.filtered() to Realm.delete() inside of a write
transaction.
2. Set a variable myDogs to all the Dog objects by passing the Dog class to
the useQuery() hook.
5. Map through the dogs to render a list of Text components that contain
a dog's name and age.
6. Add an onPress event on the "Delete Young Dog Objects" button that
calls deleteAllYoungDogObjects(), deleting all young dogs from the
realm, which triggers a re-render and removes them from the UI.
7. Add an onPress event on the "Delete All Dog Objects" button that
calls deleteAllDogObjects(), deleting every dog from the realm,
which triggers a re-render and removes them from the UI.
NOTE
When you delete objects from the realm instance, the component
automatically re-renders and removes them from the UI.
TypeScript
JavaScript
const DogList = () => {
const realm = useRealm();
const myDogs = useQuery(Dog);
const deleteAllYoungDogObjects = () => {
const youngDogs = myDogs.filtered('age < 3');
realm.write(() => {
realm.delete(youngDogs);
});
};
const deleteAllDogObjects = () => {
realm.write(() => {
realm.delete(myDogs);
});
};
return (
<>
{myDogs.map(dog => {
return (
<>
<Text>{dog.name}</Text>
<Text>{dog.age}</Text>
</>
);
})}
<Button
onPress={() => deleteAllYoungDogObjects()}
title='Delete Young Dog Objects'
/>
<Button
onPress={() => deleteAllDogObjects()}
title='Delete All Dog Objects'
/>
</>
);
};
To delete all objects from the realm, call Realm.deleteAll() inside of a write
transaction. This clears the realm of all object instances but does not affect
the realm's schema.
TIP
To filter data in your Realms, construct queries with Realm Query Language.
For more information about syntax, usage and limitations, refer to the Realm
Query Language reference.
The examples in this page use a simple data set for a task list app. The two
Realm object types are Project and Task. A Task has a name, 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 Project has zero or more Tasks.
See the schema for these two classes, Project and Task:
const TaskSchema = {
name: "Task",
properties: {
name: "string",
isComplete: "bool",
priority: "int",
progressMinutes: "int",
assignee: "string?"
}
};
const ProjectSchema = {
name: "Project",
properties: {
name: "string",
tasks: "Task[]"
}
};
Construct a Query
You can use Realm Query Language (RQL) to query on properties that have
a Full-Text Search (FTS) index. To begin, use useQuery() to return the
collection of realm objects you'd like to filter. To query the FTS indexed
property for objects within the collection, use the TEXT predicate in
your filtered query.
Exclude results for a word by placing the - character in front of the word. For
example, a search for swan -lake would include all search results
for swan excluding those with lake.
• Tokens can only consist of characters from ASCII and the Latin-1
supplement (western languages).
Operators
Comparison Operators
The following example uses the query engine's comparison operators to:
Logical Operators
The following example uses Realm Query Language's logical operators to find
all of Ali's completed tasks. We find all tasks where the assignee property
value is equal to 'Ali' AND the isComplete property value is true.
console.log(
"Number of Ali's complete tasks: " +
tasks.filtered("assignee == $0 && isComplete == $1", "Ali", true).length
);
String Operators
The following example uses Realm Query Language's string operators to find
projects with a name starting with the letter 'e' and projects with names that
contain 'ie'.
// Use [c] for case-insensitivity.
console.log(
"Projects that start with 'e': " +
projects.filtered("name BEGINSWITH[c] $0", 'e').length
);
console.log(
"Projects that contain 'ie': " +
projects.filtered("name CONTAINS $0", 'ie').length
);
Aggregate Operators
Summary
TIP
Realm does not pass any information about what changed to realm listener
callback functions. If you need to know more information about what changed
in an object or collection, use object listeners and collection listeners.
TIP
IMPORTANT
Order Matters
To remove all listeners on a given realm, object, or collection instance, call the
instance's removeAllListeners() function:
• Realm.removeAllListeners()
• Realm.Collection.removeAllListeners()
• Realm.Object.removeAllListeners()
// Remove all listeners from a realm
realm.removeAllListeners();
// Remove all listeners from a collection
dogs.removeAllListeners();
// Remove all listeners from an object
dog.removeAllListeners();
Realm provides SDKs that help you connect your client apps to the Atlas App
Services backend. The SDK provides the functionality needed to authenticate
users with any of the built-in authentication providers, call backend functions,
and directly access a linked MongoDB data source.
When using the SDK to access the App Services backend, you start with an
App object. This object provides all other functionality related to App Services.
The``App`` object is initialized with the App ID, which you can find in the App
Services UI.
TIP
To learn how to initialize the App client, see Connect to an Atlas App Services
App - React Native SDK.
TIP
To learn how to link user accounts, see Link User Identities - React Native
SDK.
To learn how to provide custom user data, see Custom User Data - React
Native SDK.
Atlas Functions enable you to define and execute server-side logic for your
application. You can call these functions from your client applications via the
Realm SDKs. These server-side functions can run under the context of the
authenticated user, and thus honor the rules, roles, and permissions that you
have assigned to your collections.
By using Functions, you provide a secure way for a variety of client
applications to share complex functionality without having to reproduce that
logic client-side.
TIP
Accessing MongoDB
The Realm SDKs include APIs for accessing a MongoDB Atlas instance
directly. With these APIs, you can perform all of the standard CRUD operations
from your client. For security, you configure server-side data access rules to
dynamically determine read & write permissions for every object that is
accessed.
TIP
The App client is the interface to the App Services backend. It provides access
to the authentication functionality, Atlas Functions, and Atlas Device Sync.
You can create multiple App client instances to connect to multiple Apps. All
App client instances that share the same App ID use the same underlying
connection.
IMPORTANT
When you initialize the App client, the configuration is cached internally.
Attempting to "close" an App and then re-open it with a changed configuration
within the same process has no effect. The client continues to use the cached
configuration.
All components wrapped within an AppProvider can access the App client
with the useApp() hook. Using the App, you can authenticate users and
access App Services.
Call a Function
IMPORTANT
Make sure to sanitize client data to protect against code injection when using
Functions.
To call a function, you can either pass its name and arguments
to User.callFunction() or call the function as if it were a method on
the User.functions property.
You can query data stored in MongoDB Atlas directly from your client
application code by using the Realm React Native SDK's MongoDB client with
the Query API. Atlas App Services provides data access rules on collections to
securely retrieve results based on the logged-in user or the content of each
document.
NOTE
Example Dataset
The examples on this page use a MongoDB collection that describes inventory
in a chain of plant stores. For more information on the collection schema and
document contents, see Example Data.
Use Cases
There are a variety of reasons you might want to query a MongoDB data
source. Working with data in your client via Atlas Device Sync is not always
practical or possible. You might want to query MongoDB when:
• The data set is large or the client device has constraints against loading
the entire data set
• Your app needs to access collections that don't have strict schemas
While not exhaustive, these are some common use cases for querying
MongoDB directly.
Prerequisites
Before you can query MongoDB from your React Native application, you must
set up MongoDB Data Access in your App Services App. To learn how to set
up your backend App to let the Realm SDK query Atlas, refer to Set Up
MongoDB Data Access in the App Services documentation.
EXAMPLE
Example Data
The examples on this page use the following MongoDB collection that
describes various plants for sale in a chain of plant stores:
{ _id: ObjectId("5f87976b7b800b285345a8c4"), name: "venus flytrap", sunlight: "full", color: "whi
{ _id: ObjectId("5f87976b7b800b285345a8c5"), name: "sweet basil", sunlight: "partial", color: "gre
{ _id: ObjectId("5f87976b7b800b285345a8c6"), name: "thai basil", sunlight: "partial", color: "gree
{ _id: ObjectId("5f87976b7b800b285345a8c7"), name: "helianthus", sunlight: "full", color: "yellow
{ _id: ObjectId("5f87976b7b800b285345a8c8"), name: "petunia", sunlight: "full", color: "purple",
JSON Schema
TypeScript
{
"title": "Plant",
"bsonType": "object",
"required": ["_id", "_partition", "name"],
"properties": {
"_id": { "bsonType": "objectId" },
"_partition": { "bsonType": "string" },
"name": { "bsonType": "string" },
"sunlight": { "bsonType": "string" },
"color": { "bsonType": "string" },
"type": { "bsonType": "string" }
}
}
To access a linked cluster from your client application, authenticate a user and
pass the cluster name to User.mongoClient(). This returns a MongoDB service
interface that you can use to access databases and collections in the cluster.
If you are using @realm/react, you can access the MongoDB client with
the useUser() hook in a component wrapped by UserProvider.
TypeScript
JavaScript
import React from 'react';
import {useUser} from '@realm/react';
function QueryPlants() {
// Get currently logged in user
const user = useUser();
const getPlantByName = async name => {
// Access linked MongoDB collection
const mongodb = user.mongoClient('mongodb-atlas');
const plants = mongodb.db('example').collection('plants');
// Query the collection
const response = await plants.findOne({name});
return response;
};
// ...
}
Read Operations
The following snippet finds the document that describes "venus flytrap" plants
in the collection of documents that describe plants for sale in a group of
stores:
The following snippet finds all documents that describe perennial plants in
the collection of documents that describe plants for sale in a group of stores:
Count Documents
Write Operations
To update a single document, pass a query that matches the document and an
update document to collection.updateOne().
To delete a single document from a collection, pass a query that matches the
document to collection.deleteOne(). If you do not pass a query or if the query
matches multiple documents, then the operation deletes the first document it
finds.
To delete multiple document from a collection, pass a query that matches the
documents to collection.deleteMany(). If you do not pass a
query, deleteMany() deletes all documents in the collection.
The following snippet deletes all documents for plants that are in "Store 51" in
a collection of documents that describe plants for sale in a group of stores:
To use collection.watch():
1. Install dependencies.
npm install react-native-polyfill-globals text-encoding
npm install --save-dev @babel/plugin-proposal-async-generator-functions
2. Import polyfills in a higher scope than where you need to use them. For
example, in index.js.
import { polyfill as polyfillReadableStream } from "react-native-polyfill-globals/src/readable-
import { polyfill as polyfillEncoding } from "react-native-polyfill-globals/src/encoding";
import { polyfill as polyfillFetch } from "react-native-polyfill-globals/src/fetch";
polyfillReadableStream();
polyfillEncoding();
polyfillFetch();
IMPORTANT
Serverless Limitations
You cannot watch for changes if the data source is an Atlas serverless
instance. MongoDB serverless currently does not support change streams,
which are used on watched collections to listen for changes.
With the @realm/react package, make sure you have configured user
authentication for your app.
TypeScript
JavaScript
import React, {useEffect} from 'react';
import Realm from 'realm';
import {useUser, useApp, AppProvider, UserProvider} from '@realm/react';
function AppWrapper() {
return (
<AppProvider id={APP_ID}>
<UserProvider fallback={<LogIn />}>
<NotificationSetter />
</UserProvider>
</AppProvider>
);
}
function NotificationSetter() {
// Get currently logged in user
const user = useUser();
const watchForAllChanges = async (
plants,
) => {
// Watch for changes to the plants collection
for await (const change of plants.watch()) {
switch (change.operationType) {
case 'insert': {
const {documentKey, fullDocument} = change;
// ... do something with the change information.
break;
}
case 'update': {
const {documentKey, fullDocument} = change;
// ... do something with the change information.
break;
}
case 'replace': {
const {documentKey, fullDocument} = change;
// ... do something with the change information.
break;
}
case 'delete': {
const {documentKey} = change;
// ... do something with the change information.
break;
}
}
}
};
useEffect(() => {
const plants = user
.mongoClient('mongodb-atlas')
.db('example')
.collection('plants');
// Set up notifications
watchForAllChanges(plants);
}, [user, watchForAllChanges]);
// ... rest of component
}
Aggregation Operations
Filter Documents
You can use the $match stage to filter documents according to standard
MongoDB query syntax.
{
"$match": {
"<Field Name>": <Query Expression>,
...
}
}
EXAMPLE
The following $match stage filters documents to include only those where
the type field has a value equal to "perennial":
const perennials = await plants.aggregate([
{ $match: { type: { $eq: "perennial" } } },
]);
console.log(perennials);
VIEW OUTPUT
Group Documents
You can use the $group stage to aggregate summary data for one or more
documents. MongoDB groups documents based on the expression defined in
the _id field of the $group stage. You can reference a specific document field
by prefixing the field name with a $.
{
"$group": {
"_id": <Group By Expression>,
"<Field Name>": <Aggregation Expression>,
...
}
}
EXAMPLE
Paginate Documents
EXAMPLE
You can use the $project stage to include or omit specific fields from
documents or to calculate new fields using aggregation operators. Projections
work in two ways:
NOTE
The _id field is a special case: it is always included in every query unless
explicitly specified otherwise. For this reason, you can exclude the _id field
with a 0 value while simultaneously including other fields, like _partition,
with a 1. Only the special case of exclusion of the _id field allows both
exclusion and inclusion in one $project stage.
{
"$project": {
"<Field Name>": <0 | 1 | Expression>,
...
}
}
EXAMPLE
The following $project stage omits the _id field, includes the name field,
and creates a new field named storeNumber. The storeNumber is generated
using two aggregation operators:
NOTE
$addFields is similar to $project but does not allow you to include or omit
fields.
EXAMPLE
You can use the $unwind stage to transform a single document containing an
array into multiple documents containing individual values from that array.
When you unwind an array field, MongoDB copies each document once for
each element of the array field but replaces the array value with the array
element in each copy.
{
$unwind: {
path: <Array Field Path>,
includeArrayIndex: <string>,
preserveNullAndEmptyArrays: <boolean>
}
}
EXAMPLE
When you use Atlas App Services to back your client app, you get access to
a user object. With this user object, you can:
You can delete user objects. Deleting a user object deletes metadata attached
to the user object, but does not delete user-entered data from the backend.
TIP
Apple requires that applications listed through its App Store must give any
user who creates an account the option to delete the account. Whether you
use an authentication method where you must manually register a user, such
as email/password authentication, or one that that automatically creates a
user, such as Sign-In with Apple, you must implement user account
deletion by June 30, 2022.
Use one or more authentication providers to log users in and out of your client
app. You can:
• Enable anonymous users to let users access your App Services App
without persisting user data.
When you have a logged-in user, SDK methods enable you to:
On successful login, the React Native SDK caches credentials on the device.
You can bypass the login flow and access the cached user. Use this to open a
realm or call a function upon subsequent app opens.
User Sessions
App Services manages sessions with access tokens and refresh tokens. Client
SDKs supply the logic to manage tokens and provide them with requests.
Realm uses refresh tokens to automatically update a user's access token when
it expires. However, Realm does not automatically refresh the refresh token.
When the refresh token expires, the SDK can no longer get an updated access
token and the device cannot sync until the user logs in again.
For more information on managing user sessions and tokens, see User
Sessions in the App Services documentation.
You can associate custom data with a user object, such as a preferred
language or local timezone, and read it from your client application. A user
object has a customData property that you can use to access custom user
data.
To create and update custom user data, you must access your MongoDB data
source directly. App Services does not offer a SDK method to create or update
this custom user data; it's a read-only property.
Create a User
IMPORTANT
Google and Apple require that applications listed through their respective App
Stores must give any user who creates an account the option to delete the
account. Whether you use an authentication method where you must manually
register a user, such as email/password authentication, or one that that
automatically creates a user, such as Sign-In with Apple, you must
implement user account deletion.
Delete a User
Call the App.deleteUser() on a user object to delete the user's account from
your Realm application. This deletes the account from the server in addition to
clearing local data.
import React, {useState, useEffect} from 'react';
import {useApp, useUser} from '@realm/react';
function DeleteUser() {
const app = useApp();
const user = useUser();
async function deleteUser() {
// Delete the currently logged in user
await app.deleteUser(user);
}
// ...
}
To use your app in the future, the user must sign up for a new account. They
can use the same credentials (depending on the authentication provider), but
will not have the same User ID as their deleted account.
IMPORTANT
Deleting a user only deletes the user object, which may contain associated
metadata from the associated auth provider. This does not delete custom user
data or other user data that your app stores in a linked collection or external
services.
Google and Apple require that you disclose data retention and deletion
policies to your application customers and give them a way to request user
data deletion. If you collect additional user data, you must implement your own
methods or processes to delete that data.
Prerequisites
Log In
User Sessions
The React Native SDK communicates with Atlas App Services to manage
sessions with access tokens and refresh tokens.
To learn more about session management, refer to User Sessions in the App
Services documentation.
Anonymous User
Email/Password User
To log in, create an email/password credential with the user's email address
and password and pass it to App.logIn():
To log in with an API key, create an API Key credential with a server or user
API key and pass it to App.logIn():
To log in, create a Custom JWT credential with a JWT from the external system
and pass it to App.logIn():
Facebook User
To log a user in with their existing Facebook account, you must configure and
enable the Facebook authentication provider for your App Services App.
IMPORTANT
Facebook profile picture URLs include the user's access token to grant
permission to the image. To ensure security, do not store a URL that includes
a user's access token. Instead, access the URL directly from the user's
metadata fields when you need to fetch the image.
You can use the official Facebook SDK to handle the user authentication and
redirect flow from a client application. Once authenticated, the Facebook SDK
returns an access token that you can send to your React Native app and use to
finish logging the user in to your app.
// Get the access token from a client application using the Facebook SDK
const accessToken = getFacebookAccessToken();
// Log the user in to your app
const credentials = Realm.Credentials.facebook(accessToken);
app.logIn(credentials).then(user => {
console.log(`Logged in with id: ${user.id}`);
});
Google User
The Google authentication provider allows you to authenticate users with their
existing Google account.
EXAMPLE
This example uses the library React Native Google Sign In. In addition to the
React Native code, you must also set up additional configuration in your
project's ios and android directories to use Sign in with Google. Refer to the
package's documentation for iOS-specific and Android-
specific documentation.
SignInWithGoogleButton.jsx
import { useState } from "react";
import {
GoogleSignin,
GoogleSigninButton,
statusCodes,
} from "@react-native-google-signin/google-signin";
import Realm from "realm";
// Instantiate Realm app
const app = new Realm.App({
id: "<Your App ID>",
});
// Configure Google Auth
GoogleSignin.configure({
webClientId: "<Your Web Client ID>",
});
export default function GoogleSignInButton() {
const [signinInProgress, setSigninInProgress] = useState(false);
const signIn = async () => {
setSigninInProgress(true);
try {
// Sign into Google
await GoogleSignin.hasPlayServices();
const { idToken } = await GoogleSignin.signIn();
// use Google ID token to sign into Realm
const credential = Realm.Credentials.google({ idToken });
const user = await app.logIn(credential);
console.log("signed in as Realm user", user.id);
} catch (error) {
// handle errors
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
// user cancelled the login flow
} else if (error.code === statusCodes.IN_PROGRESS) {
// operation (e.g. sign in) is in progress already
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
// play services not available or outdated
} else {
// some other error happened
}
} finally {
setSigninInProgress(false);
}
};
// return Google Sign in button component
return (
<GoogleSigninButton
style={{ width: 192, height: 48 }}
size={GoogleSigninButton.Size.Wide}
color={GoogleSigninButton.Color.Dark}
onPress={signIn}
disabled={signinInProgress}
/>
);
}
Apple User
You can use the official Sign in with Apple JS SDK to handle the user
authentication and redirect flow from a client application. Once authenticated,
the Apple JS SDK returns an ID token that you can send to your React Native
app and use to finish logging the user in to your app.
// Get the access token from a client application using the Apple JS SDK
const idToken = getAppleIdToken();
// Log the user in to your app
const credentials = Realm.Credentials.apple(idToken);
app.logIn(credentials).then(user => {
console.log(`Logged in with id: ${user.id}`);
});
TIP
If you get a Login failed error saying that the token contains an
invalid number of segments, verify that you're passing a UTF-8-encoded
string version of the JWT.
Offline Login
NOTE
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.
// Log user into your App Services App.
// On first login, the user must have a network connection.
const getUser = async () => {
// If the device has no cached user credentials, log in.
if (!app.currentUser) {
const credentials = Realm.Credentials.anonymous();
await app.logIn(credentials);
}
// If the app is offline, but credentials are
// cached, return existing user.
return app.currentUser!;
};
To learn how to use the cached user in the Sync Configuration and access a
realm while offline, read the Open a Synced Realm While Offline docs.
When a user logs in, Atlas App Services creates an access token for the user
that grants them access to your App. The Realm SDK automatically manages
access tokens, refreshes them when they expire, and includes a valid access
token for the current user with each request. Realm does not automatically
refresh the refresh token. When the refresh token expires, the user must log in
again.
If you send requests outside of the SDK (for example, through the GraphQL
API) then you need to include the user's access token with each request, and
manually refresh the token when it expires.
You can access and refresh a logged in user's access token in the SDK from
their Realm.User object, as in the following example:
TypeScript
JavaScript
// Gets a valid user access token to authenticate requests
async function getValidAccessToken(user) {
// An already logged in user's access token might be stale. To
// guarantee that the token is valid, refresh it if necessary.
await user.refreshCustomData();
return user.accessToken;
}
If the refresh token expires after the realm is open, the device will not be able
to sync until the user logs in again. Your sync error handler should implement
logic that catches a token expired error when attempting to sync, then redirect
users to a login flow.
To log any user out, call the User.logOut() on their user instance.
WARNING
When a user logs out, you can no longer read or write data in any synced
realms that the user opened. As a result, any operation that has not yet
completed before the initiating user logs out cannot complete successfully
and will likely result in an error. Any data in a write operation that fails in this
way will be lost.
// Log out the current user
await app.currentUser?.logOut();
You can read arbitrary data about your application users, known as custom
user data, directly within your React Native application. For example, you
might store a user's preferred language, date of birth, or local timezone.
Prerequisites
Before you can work with custom user data from your React Native app, you
must enable it in your App Services App. To learn more, refer to Enable
Custom User Data.
WARNING
App Services does not dynamically update a user's custom data if the
underlying document changes. Instead, App Services fetches a new copy of
the data whenever a user refreshes their access token, such as when they log
in. This may mean that the custom data won't immediately reflect changes, e.g.
updates from an authentication Trigger. If the token is not refreshed, the SDK
waits 30 minutes and then refreshes it on the next call to the backend, so
custom user data could be stale for up to 30 minutes plus the time until the
next SDK call to the backend occurs.
If you have not recently updated your custom user data, use the user
object's customData field. The customData field is read only.
If you have updated your custom user data within the last 30 minutes,
use User.refreshCustomData().
You can write to a user's custom user data with MongoDB Data Access.
Your write operations must include the User.id in the User ID Field you set
when configuring custom user data in the App backend. If you don't include
the user ID in the User ID Field, the data that you write will not be linked to the
user's custom user data.
NOTE
To modify the custom data field from a client or user function, write
permission to the collection in which custom data is stored must be
configured. If you prefer to restrict client write access to custom data from
your application, you can still modify the object from a system function.
Prerequisites
Before you can manage email/password users from your React Native app,
you must enable email/password authentication in your App Services App
backend. To learn more about configuring email/password authentication,
refer to Email/Password Authentication in the App Services documentation.
TypeScript
JavaScript
import React from 'react';
import {useApp, UserProvider, AppProvider} from '@realm/react';
import Realm from 'realm';
function AppWrapper() {
return (
<View>
<AppProvider id={APP_ID}>
<UserProvider fallback={<RegisterUser />}>
{/* ...Other components in app that require authentication */}
</UserProvider>
</AppProvider>
</View>
);
}
function RegisterUser() {
const app = useApp();
async function register(email, password) {
// Register new email/password user
await app.emailPasswordAuth.registerUser({email, password});
// Log in the email/password user
await app.logIn(Realm.Credentials.emailPassword(email, password));
}
// ...
}
To register a new email/password user, pass the user's email address and
desired password to EmailPasswordAuth.registerUser(). The email address
must not be associated with another email/password user and the password
must be between 6 and 128 characters.
After registration, you must confirm a new user's email address before they
can log in to your app.
await app.emailPasswordAuth.registerUser({
email: "[email protected]",
password: "Pa55w0rd!",
});
New users must confirm that they own their email address before they can log
in to your app unless the provider is configured to automatically confirm new
users. The confirmation process starts when you register a user and ends
when you confirm them from your client code.
You need a valid token and tokenId for a registered user in order to confirm
them and allow them to log in. These values are available in different places
depending on the provider configuration:
The SDK provides methods to resend user confirmation emails or retry custom
confirmation methods.
1. In your client app, you provide a UI for the user to reset their password.
Your App Services App can then send an email or run a custom function
to confirm the user's identity.
2. After confirming the user's identity, you can complete the password
reset request.
3. After the password reset is complete, the user can log in using the new
password.
For more information about how to set your preferred password reset method,
refer to the App Services Email/Password Authentication documentation.
To send password reset emails to confirm the user's identity, you must
configure your App to send a password reset email.
After the user has visited the URL from the password reset email,
call EmailPasswordAuth.resetPassword() with the user's email, the new
password, and the token and tokenId provided in the unique URL.
await app.emailPasswordAuth.resetPassword({
password: "newPassw0rd",
token,
tokenId,
});
If the user does not visit the URL from the password reset email within 30
minutes, the token and tokenId expire. You must begin the password reset
process again.
When you configure your app to run a password reset function, you define the
function that should run when you
call EmailPasswordAuth.callResetPasswordFunction().
This function can take a username, a password, and any number of additional
arguments. You can use these arguments to specify details like security
question answers or other challenges that the user should pass to
successfully complete a password reset.
You might prefer to use a custom password reset function when you want to
define your own password reset flows. For example, you might send a custom
password reset email from a specific domain. Or you might use a service other
than email to confirm the user's identity.
On the App Services side, you define the custom password reset function that
runs when you call this method. That function can return one of three possible
statuses:
• fail
• pending
• success
A fail status is treated as an error by the SDK. The
SDK callResetPasswordFunction() does not take return values, so it does
not return a pending or success status to the client.
Your App Services password reset function may return pending if you want
the user to take some additional step to confirm their identity. However, that
return value is not passed to the SDK's callResetPasswordFunction(), so
your client app must implement its own logic to handle a pending status.
Your server-side function might send an email using a custom email provider.
Or you may use SMS to confirm the user's identity via text message.
You have access to a token and tokenId in the App Services password reset
function context. If you pass this information from your App Services
password reset function, you can pass these values back to your app using
platform-specific deep linking or universal links. Then, your client application
can call EmailPasswordAuth.resetPassword() to complete the password reset
flow.
await app.emailPasswordAuth.resetPassword({
password: "newPassw0rd",
token,
tokenId,
});
If your App Services password reset function does additional validation within
the function, or if you have validated the user's identity prior to attempting to
reset the password, you may configure the App Services function to
return success. However, that return value is not passed to the
SDK's callResetPasswordFunction(), so your client app must implement
its own logic to handle a success status.
Calling the function in this example performs the entire password reset
process.
await app.emailPasswordAuth.resetPassword({
password: "newPassw0rd",
token,
tokenId,
});
IMPORTANT
Any logged-in user may become the active user without re-
authenticating. Depending on your app, this may be a security vulnerability.
For example, a user on a shared device may switch to a coworker's logged in
account without providing their credentials or requiring their explicit
permission. If your application requires stricter authentication, avoid switching
between users and prefer to explicitly log the active user out before
authenticating another user.
When a user first logs in through a Realm SDK on a device, the SDK saves the
user's information and keeps track of the user's state on the device. The user's
data remains on the device, even if they log out, unless you actively remove
the user.
• Authenticated: any user that has logged in on the device and has not
logged out or had its session revoked.
o Inactive: all authenticated users that are not the current active
user. You can switch the active user to a currently inactive user at
any time.
• Logged Out: any user that authenticated on the device but has since
logged out or had their session revoked.
The following diagram shows how users within an App Services client app
transition between states when certain events occur:
Before You Begin
If you're using @realm/react, you must wrap any components that you want
to manage users with in the AppProvider component. Components wrapped
with an AppProvider can use the useApp() hook to access
the Realm.App client.
The Realm SDK automatically adds users to a device when they log in for the
first time on that device. When a user logs in, they immediately become the
application's active user.
// Log in as Joe
const joeCredentials = Realm.Credentials.emailPassword("[email protected]", "passw0rd")
const joe = await app.logIn(joeCredentials);
// The active user is now Joe
assert(joe.id === app.currentUser.id);
// Log in as Emma
const emmaCredentials = Realm.Credentials.emailPassword("[email protected]", "pa55word")
const emma = await app.logIn(emmaCredentials);
// The active user is now Emma, but Joe is still logged in
assert(emma.id === app.currentUser.id);
You can remove all information about a user from the device and automatically
log the user out with Realm.App.removeUser(). This method does not delete
the user from the backend App.
You can quickly switch an app's active user to another logged-in user at any
time with Realm.App.switchUser().
Realm provides many authentication providers to log users into your app.
Each provider creates a unique user identity. Realm lets you merge multiple
credentials into one user identity.
User API keys allow devices or services to communicate with App Services on
behalf of a user without sharing that users credentials. User API keys can be
revoked at any time by the authenticated user. User API keys do not expire on
their own.
You can manage a user API key with the ApiKeyAuth client accessed with an
authenticated user's User.apiKeys property.
If you are using @realm/react, you can access a user's ApiKeyAuth client
with the useUser() hook in a component wrapped by UserProvider.
To create a new user API key, pass a name that's unique among all of the
user's API keys to ApiKeyAuth.create().
The SDK returns the value of the user API key when you create it. Make sure to
store the key value securely so that you can use it to log in. If you lose or do
not store the key value there is no way to recover it. You will need to create a
new user API key.
You cannot create a user API key for a server API key or an anonymous user.
const key = await user.apiKeys.create("apiKeyName");
To get an array that lists all of a user's API keys, call ApiKeyAuth.fetchAll().
TIP
See also:
Flexible Sync
When you select Flexible Sync for your backend App configuration, your client
implementation must include subscriptions to queries on queryable fields.
Flexible Sync works by synchronizing data that matches query subscriptions
you maintain in the client application.
NOTE
Flexible Sync does not support all the query operators available in Realm
Query Language and the SDK's query engine. See Flexible Sync RQL
Limitations for details.
Subscription sets are based on a specific type of Realm object. You might
have multiple subscriptions if you have many types of Realm objects.
To use Flexible Sync in your client application, open a synced realm with a
Flexible Sync configuration. Then, manage subscriptions to determine which
documents to sync.
TIP
See also:
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.
TIP
Device Sync supports two Sync Modes: Flexible Sync, and the older Partition-
Based Sync. If your App Services backend uses Partition-Based Sync, refer
to Partition-Based Sync - React Native SDK.
Prerequisites
Before you configure a realm with Flexible Sync in a React Native application:
1. Enable Flexible Sync on the backend. You must configure Flexible Sync
in the backend before you can use it with your client application.
By default, Realm syncs all data from the server before returning anything. If
you want to sync data in the background, read Configure a Synced Realm
While Offline.
2. Configure AppProvider.
NOTE
Partition-Based Sync
This page covers Flexible Sync realms. Flexible Sync is the preferred mode for
new apps that use Atlas Device Sync. For information about realms using the
older Partition-Based Sync, refer to Partition-Based Sync.
AppProvider
To set up your App client, pass the App ID string to the id prop
of AppProvider. Wrap any components that need to access the App with
the AppProvider.
You can find more information about AppProvider on the Connect To Atlas
App Services page.
UserProvider
RealmProvider
2. Configure AppProvider.
<RealmProvider
schema={[YourObjectModel]}
sync={{
flexible: true,
initialSubscriptions: {
update(subs, realm) {
subs.add(realm.objects(YourObjectModel));
},
},
}}>
<RestOfApp />
</RealmProvider>
</UserProvider>
</AppProvider>
);
}
For more information about configuring and using RealmProvider, check out
the Configure a Realm page.
Configuration Options
You can configure RealmProvider by setting props that match the properties
of a Configuration object. You can also set fallback and realmRef props.
• realmRef
Used with useRef to expose the configured realm to processes outside
of RealmProvider. This can be useful for things like a client reset fallback.
• fallback
Rendered while waiting for the realm to open. Local realms usually open fast
enough that the fallback prop isn't needed.
TypeScript
JavaScript
import React from 'react';
import {AppProvider, UserProvider, RealmProvider} from '@realm/react';
function AppWrapperSync({customBaseFilePath}) {
return (
<UserProvider fallback={LogIn}>
<RealmProvider
path={customRealmPath}
schema={[Profile]}
sync={{
flexible: true,
}}>
<RestOfApp />
</RealmProvider>
</UserProvider>
</AppProvider>
);
}
Where, exactly, your Realm file is stored can vary depending on how you
set Realm.BaseConfiguration.path:
• Realm.Configuration.path is not set and baseFilePath is set. Your
Realm file is stored at baseFilePath.
You can open a realm immediately with background sync or after a timeout.
NOTE
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 may want to sync changes in the background to display partial data to the
user while the synced realm downloads data from the server, preventing the
user experience from being blocked. We recommend syncing changes in the
background for applications in which the user's device may go offline. To sync
changes in the background, open a synced realm synchronously.
TypeScript
JavaScript
import React from 'react';
import {AppProvider, UserProvider, RealmProvider} from '@realm/react';
function AppWrapperOfflineSync() {
const realmAccessBehavior = {
type: 'openImmediately',
};
return (
<AppProvider id={APP_ID}>
<UserProvider fallback={LogIn}>
<RealmProvider
schema={[Profile]}
sync={{
flexible: true,
newRealmFileBehavior: realmAccessBehavior,
existingRealmFileBehavior: realmAccessBehavior,
}}>
<RestOfApp />
</RealmProvider>
</UserProvider>
</AppProvider>
);
}
If the realm doesn't finish downloading before the timeout, the initial Sync
continues in the background.
TypeScript
JavaScript
import React from 'react';
import {AppProvider, UserProvider, RealmProvider} from '@realm/react';
function AppWrapperTimeoutSync() {
const realmAccessBehavior = {
type: 'downloadBeforeOpen',
timeOutBehavior: 'openLocalRealm',
timeOut: 1000,
};
return (
<AppProvider id={APP_ID}>
<UserProvider fallback={LogIn}>
<RealmProvider
schema={[Profile]}
sync={{
flexible: true,
newRealmFileBehavior: realmAccessBehavior,
existingRealmFileBehavior: realmAccessBehavior,
}}>
<RestOfApp />
</RealmProvider>
</UserProvider>
</AppProvider>
);
}
@realm/react has providers and hooks that simplify working with your
synced realm and its data.
AppProvider
UserProvider
Provider/Hook Description Example
UserProvider React component that provides a Realm user for the See UserProvider
Sync hooks. A UserProvider is required for an app to use
Sync hooks.
useUser Accesses the currently-authenticated Realm user from const user = useUse
the UserProvider context. The user is stored as React
state and will trigger a re-render whenever it changes.
RealmProvider
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.
IMPORTANT
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.
Prerequisites
You need to meet the following requirements before you can use Atlas Device
Sync with the React Native SDK:
Before you can add Flexible Sync subscriptions to a React Native client, you
must:
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 the limitations of using Realm Query Language with
Flexible Sync, refer to the Flexible Sync RQL Limitations section.
Subscribe to Queries
For all subscriptions, you need an authenticated user and a Flexible Sync
realm.
Subscribe to a Query
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.
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
• subscribe()
• SubscriptionOptions
• null
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. You can specify
different behavior for your subscriptions and how they handle writing for
downloads.
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.
You can optionally specify a timeout value to limit how long the sync
download runs:
API Reference
• waitForSync
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.
API Reference
• unsubscribe()
You can use the Subscriptions API to manually manage a set of subscriptions
to specific queries on queryable fields.
• Add subscriptions
• Remove 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.
API Reference
• SubscriptionSet
Add a Subscription
• Completed tasks
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.
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
• FlexibleSyncConfiguration
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:
• 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>
);
}
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 Realm SDK does not provide a way to check whether all documents that
match a subscription have synced to the device.
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
API Reference
• MutableSubscriptionSet.add()
• SubScriptionSet.update()
• SubscriptionOptions.throwOnUpdate
Remove Subscriptions
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.
When you remove a subscription query, the server also removes synced data
from the client device.
realm.subscriptions.update((mutableSubs) => {
// remove a subscription with a specific query
mutableSubs.remove(tasks.filtered('owner == "Ben"'));
});
realm.subscriptions.update((mutableSubs) => {
// remove a subscription with a specific name
mutableSubs.removeByName("longRunningTasksSubscription");
});
let subscriptionReference;
realm.subscriptions.update((mutableSubs) => {
subscriptionReference = mutableSubs.add(realm.objects("Task"));
});
// later..
realm.subscriptions.removeSubscription(subscriptionReference);
Remove All Subscriptions on an Object Type
realm.subscriptions.update((mutableSubs) => {
mutableSubs.removeByObjectType("Team");
});
You can remove all unnamed subscriptions from the subscription set by
calling .removeUnnamed() on mutableSubs. .removeUnnamed() returns the
number of unnamed subscriptions removed.
API Reference
• removeUnnamed()
realm.subscriptions.update((mutableSubs) => {
mutableSubs.removeAll();
});
Performance Considerations
API Efficiency
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 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.
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
List Queries
You can query a list of constants to see if it contains the value of a queryable
field:
If a queryable field has an array value, you can query to see if it contains a
constant value:
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
When you use Atlas Device Sync, the React Native SDK syncs data with Atlas
in the background using a sync session. A sync session starts whenever you
open a synced realm.
Prerequisites
Before you can manage a sync session, you must perform the following:
3. Wrap components that use the useRealm() hook for a synced realm
with the AppProvider, UserProvider,
and RealmProvider components. For more information on configuring
and opening a synced realm, refer to Open a Synced Realm.
Opening a synced realm starts a sync session. You can pause and resume the
realm's sync session. If you have more than one open realm, pause does not
affect the other realms' sync sessions.
For most applications, there is no need to manually pause and resume a sync
session. However, there are a few circumstances under which you may want to
pause or suspend a sync session:
• You only want to sync after the user takes a specific action
• If the client may be offline longer than the client maximum offline time,
the client will be unable to resume syncing and must perform a client
reset.
Do not pause sync to stop syncing for indefinite time periods or time ranges in
months and years. The functionality is not designed or tested for these use
cases. You could encounter a range of issues when using it this way.
Check Upload & Download Progress for a Sync Session
To check the upload and download progress for a sync session, add a
progress notification using
the Realm.syncSession.addProgressNotification() method.
NOTE
Flexible Sync progress notifications are not yet fully supported. When using
Flexible Sync, downloads only report notifications after changes are
integrated. Partition-Based Sync provides ongoing notifications as changes
progress downloading. Uploads report ongoing progress notifications for both
Sync Modes.
Realm's offline-first design means that you generally don't need to check the
current network connection state since data syncs in the background when a
connection is available. That said, the Realm SDK provides methods to get the
current state of the network connection to the server.
TypeScript
JavaScript
import React, {useState, useEffect} from 'react';
import {SyncedRealmContext} from '../RealmConfig';
const {useRealm} = SyncedRealmContext;
import {Text} from 'react-native';
function CheckNetworkConnection() {
const realm = useRealm();
const [isConnected, setIsConnected] = useState(
realm.syncSession?.isConnected(),
);
useEffect(() => {
const connectionNotificationCallback = (newState, oldState) => {
console.log('Current connection state: ' + newState);
console.log('Previous connection state: ' + oldState);
setIsConnected(realm.syncSession?.isConnected());
};
// Listen for changes to connection state
realm.syncSession?.addConnectionNotification(
connectionNotificationCallback,
);
// Remove the connection listener when component unmounts
return () =>
realm.syncSession?.removeConnectionNotification(
connectionNotificationCallback,
);
// Run useEffect only when component mounts
}, []);
return (
<Text>
{isConnected ? 'Connected to Network' : 'Disconnected from Network'}
</Text>
);
}
While developing an application that uses Device Sync, you should set an
error handler. This error handler will detect and respond to any failed sync-
related API calls.
Set an error handler by registering an error callback as part of
the FlexibleSyncConfiguration. If you're using @realm/react, pass
the SyncConfiguration object to the sync property of a RealmProvider.
const syncConfigWithErrorHandling = {
flexible: true,
onError: (_session, error) => {
console.log(error);
},
};
function RealmWithErrorHandling() {
return (
<RealmProvider sync={syncConfigWithErrorHandling}>
<RestOfApp />
</RealmProvider>
);
}
TIP
For a list of common Device Sync errors and how to handle them, refer to Sync
Errors in the App Services Device Sync documentation.
A client reset error is a type of sync error where a client realm cannot sync
data with the Atlas App Services backend. Clients in this state may continue to
run and save data locally but cannot send or receive sync changesets until
they perform a client reset.
To learn about the causes of and modes for handling client resets, refer
to Device Sync Client Resets in the App Services documentation.
Client Reset Modes
You can specify which client reset mode your app should use to restore the
realm to a syncable state:
• Recover unsynced changes mode: When you choose this mode, the
client attempts to recover unsynced changes. Choose this mode when
you do not want to fall through to discard unsynced changes.
• Manual recovery mode: Downloads a new copy of the realm, and moves
the unsyncable realm to a backup. Migrate unsynced data from the
backup copy of the realm to the new syncable copy.
The Realm SDKs provide client reset modes that automatically handle most
client reset errors.
Automatic client reset modes restore your local realm file to a syncable state
without closing the realm or missing notifications. The following client reset
modes support automatic client resets:
If your app requires specific client reset logic that can't be handled
automatically, you may want or need to add a manual client reset handler to
the automatic client reset mode.
To use Client Recovery, configure your realm with one of the following client
reset modes:
When Client Recovery is enabled, these rules determine how objects are
integrated, including how conflicts are resolved when both the backend and
the client make changes to the same object:
• Objects created locally that were not synced before client reset are
synced.
• If an object is deleted on the server, but is modified on the recovering
client, the delete takes precedence and the client discards the update.
• If an object is deleted on the recovering client, but not the server, then
the client applies the server's delete instruction.
• In the case of conflicting updates to the same field, the client update is
applied.
Client Recovery cannot succeed when your app makes breaking schema
changes. A breaking change is a change that you can make in your server-side
schema that requires additional action to handle. In this scenario, client reset
falls back to a manual error client reset fallback.
When you choose recover unsynced changes mode, the client attempts to
recover unsynced changes with Client Recovery. Choose this mode when you
do not want to fall through to discard unsynced changes.
To handle client resets with the recover unsynced changes mode, pass
a ClientResetConfiguration to the clientReset field of
your FlexibleSyncConfiguration. Include these properties in
the ClientResetConfiguration:
TypeScript
JavaScript
const syncConfigWithRecoverClientReset = {
flexible: true,
clientReset: {
mode: 'recoverUnsyncedChanges',
onBefore: realm => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onAfter: (beforeRealm, afterRealm) => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onFallback: (session, path) => {
// See below "Manual Client Reset Fallback" section for example
},
},
};
function RealmWithRecoverUnsyncedChangesClientReset() {
return (
<RealmProvider sync={syncConfigWithRecoverClientReset}>
<RestOfApp />
</RealmProvider>
);
}
To handle client resets with the recover or discard unsynced changes mode,
pass a ClientResetConfiguration to the clientReset field of
your FlexibleSyncConfiguration. Include these properties in
the ClientResetConfiguration:
TypeScript
JavaScript
const syncConfigWithRecoverDiscardClientReset = {
flexible: true,
clientReset: {
mode: 'recoverOrDiscardUnsyncedChanges',
onBefore: realm => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onAfter: (beforeRealm, afterRealm) => {
// This block could be used for custom recovery, reporting, debugging etc.
},
onFallback: (session, path) => {
// See below "Manual Client Reset Fallback" section for example
},
},
};
function RealmWithRecoverOrDiscardUnsyncedChangesClientReset() {
return (
<RealmProvider sync={syncConfigWithRecoverDiscardClientReset}>
<RestOfApp />
</RealmProvider>
);
}
If the client reset with recovery cannot complete automatically, like when there
are breaking schema changes, the client reset process falls through to a
manual error handler. This may occur in either of the client reset with recovery
modes, recover unsynced changes and recover or discard unsynced changes.
You must provide a manual client reset implementation in
the SyncConfiguration.onFallback() callback. onFallback() takes two
arguments:
The following example demonstrates how you can manually handle this error
case by discarding all unsynced changes:
TypeScript
JavaScript
let realm; // value assigned in <RestOfApp> with useRealm()
const syncConfigWithClientResetFallback = {
flexible: true,
clientReset: {
mode: 'recoverOrDiscardUnsyncedChanges', // or "recoverUnsyncedChanges"
// can also include `onBefore` and `onAfter` callbacks
onFallback: (_session, path) => {
try {
// Prompt user to perform a client reset immediately. If they don't,
// they won't receive any data from the server until they restart the app
// and all changes they make will be discarded when the app restarts.
const didUserConfirmReset = showUserAConfirmationDialog();
if (didUserConfirmReset) {
// Close and delete old realm from device
realm.close();
Realm.deleteFile(path);
// Perform client reset
Realm.App.Sync.initiateClientReset(app, path);
// Navigate the user back to the main page or reopen the
// the Realm and reinitialize the current page
}
} catch (err) {
// Reset failed. Notify user that they'll need to
// update the app
}
},
},
};
function RealmWithManualClientResetFallback() {
return (
<RealmProvider sync={syncConfigWithClientResetFallback}>
<RestOfApp />
</RealmProvider>
);
}
function RestOfApp() {
// Assigning variable defined above to a realm.
realm = useRealm();
return <>{/* Other components in rest of app */}</>;
}
Do not use discard unsynced changes mode if your application cannot lose
local data that has not yet synced to the backend.
To handle client resets with the discard unsynced changes mode, pass
a ClientResetConfiguration to the clientReset field of
your FlexibleSyncConfiguration. Include these properties in
the ClientResetConfiguration:
TypeScript
JavaScript
const syncConfigWithDiscardClientReset = {
flexible: true,
clientReset: {
mode: 'discardUnsyncedChanges',
onBefore: realm => {
console.log('Beginning client reset for ', realm.path);
},
onAfter: (beforeRealm, afterRealm) => {
console.log('Finished client reset for', beforeRealm.path);
console.log('New realm path', afterRealm.path);
},
},
};
function RealmWitDiscardUnsyncedChangesClientReset() {
return (
<RealmProvider sync={syncConfigWithDiscardClientReset}>
<RestOfApp />
</RealmProvider>
);
}
TypeScript
JavaScript
// Once you have opened your Realm, you will have to keep a reference to it.
// In the error handler, this reference is called `realm`
async function handleSyncError(session, syncError) {
if (syncError.name == 'ClientReset') {
console.log(syncError);
try {
console.log('error type is ClientReset....');
const path = realm.path; // realm.path will not be accessible after realm.close()
realm.close();
Realm.App.Sync.initiateClientReset(app, path);
// Download Realm from the server.
// Ensure that the backend state is fully downloaded before proceeding,
// which is the default behavior.
realm = await Realm.open(config);
realm.close();
} catch (err) {
console.error(err);
}
} else {
// ...handle other error types
}
}
const syncConfigWithDiscardAfterBreakingSchemaChanges = {
flexible: true,
clientReset: {
mode: 'discardUnsyncedChanges',
onBefore: realm => {
// NOT used with destructive schema changes
console.log('Beginning client reset for ', realm.path);
},
onAfter: (beforeRealm, afterRealm) => {
// Destructive schema changes do not hit this function.
// Instead, they go through the error handler.
console.log('Finished client reset for', beforeRealm.path);
console.log('New realm path', afterRealm.path);
},
},
onError: handleSyncError, // invoked with destructive schema changes
};
function RealmWitDiscardAfterBreakingSchemaChangesClientReset() {
return (
<RealmProvider sync={syncConfigWithDiscardAfterBreakingSchemaChanges}>
<RestOfApp />
</RealmProvider>
);
}
NOTE
If you'd like to attempt to recover unsynced changes, but but discard any
changes that cannot be recovered, refer to the recover or discard unsynced
changes mode section.
Manual Mode
In manual mode, you define your own client reset handler. You might want to
use a manual client reset handler if the Automatic Recovery logic does not
work for your app and you can't discard unsynced local data.
When you terminate and re-enable Sync, clients that have previously
connected with Sync are unable to connect until after they perform a client
reset. Terminating Sync deletes the metadata from the server that allows the
client to synchronize. The client must download a new copy of the realm from
the server. The server sends a client reset error to these clients. So, when you
terminate Sync, you trigger the client reset condition.
3. Run the client app again. The app should get a client reset error when it
tries to connect to the server.
WARNING
While you iterate on client reset handling in your client application, you may
need to terminate and re-enable Sync repeatedly. Terminating and re-enabling
Sync renders all existing clients unable to sync until after completing a client
reset. To avoid this in production, test client reset handling in a
development environment.
This page explains how to manually recover unsynced realm data after a client
reset using the Manual Recovery client reset mode.
For more information about the other available client reset modes, refer
to Reset a Client Realm.
The specifics of manual recovery depend heavily upon your application and
your schema. However, there are a few techniques that can help with manual
recoveries. The Track Changes by Object Strategy section demonstrates one
method of recovering unsynced changes during a client reset.
WARNING
Do not expect to recover all unsynced data after a breaking schema change.
The best way to preserve user data is to never make a breaking schema
change at all.
IMPORTANT
The track changes by object manual client reset data recovery strategy lets
you recover data already written to the client realm file but not yet synced to
the backend.
In this strategy, you add a "Last Updated Time" to each object model to track
when each object last changed. We'll watch the to determine when the realm
last uploaded its state to the backend.
When backend invokes a client reset, find objects that were deleted, created,
or updated since the last sync with the backend. Then copy that data from the
backup realm to the new realm.
1. Client reset error: Your application receives a client reset error code
from the backend.
3. Close all instances of the realm: Close all open instances of the realm
experiencing the client reset. If your application architecture makes this
difficult (for instance, if your app uses many realm instances
simultaneously in listeners throughout the application), it may be easier
to restart the application. You can do this programmatically or through a
direct request to the user in a dialog.
5. Open new instance of the realm: Open a new instance of the realm using
your typical sync configuration. If your application uses multiple realms,
you can identify the realm experiencing a client reset from the backup
file name.
6. Download all realm data from the backend: Download the entire set of
data in the realm before you proceed. This is the default behavior of
the FlexibleSyncConfiguration object.
Example
NOTE
• This example keeps track of the last time each object was updated. As a
result, the recovery operation overwrites the entire object in the new
realm if any field was updated after the last successful sync of the
backup realm. This could overwrite fields updated by other clients with
old data from this client. If your realm objects contain multiple fields
containing important data, consider keeping track of the last updated
time of each field instead, and recovering each field individually.
For more information on other ways to perform a manual client reset with data
recovery, refer to the Alternative Strategies section.
Add a new property to your Realm object schema to track the last time it was
updated. Whenever you create or update a Realm object with the schema,
include a timestamp with the update time.
Ordinarily, there is no way to detect when a Realm object was last modified.
This makes it difficult to determine which changes were synced to the
backend. By adding a timestamp lastUpdated to your Realm object models
and updating that timestamp to the current time whenever a change occurs,
you can keep track of when objects were changed.
const DogSchema = {
name: "Dog",
properties: {
name: "string",
age: "int?",
lastUpdated: "int",
},
};
2
const config = {
schema: [DogSchema],
sync: {
user: app.currentUser,
partitionValue: "MyPartitionValue",
clientReset: {
mode: "manual",
},
error: handleSyncError, // callback function defined later
},
};
3
Just knowing when objects were changed isn't enough to recover data during
a client reset. You also need to know when the realm last completed a sync
successfully. This example implementation uses a singleton object in a
separate realm called LastSynced paired with a change listener to record
when a realm finishes syncing successfully.
Define your LastSynced Realm to track the latest time your realm
synchronizes.
const LastSyncedSchema = {
name: "LastSynced",
properties: {
realmTracked: "string",
timestamp: "int?",
},
primaryKey: "realmTracked",
};
const lastSyncedConfig = { schema: [LastSyncedSchema] };
const lastSyncedRealm = await Realm.open(lastSyncedConfig);
lastSyncedRealm.write(() => {
lastSyncedRealm.create("LastSynced", {
realmTracked: "Dog",
});
});
Now that you've recorded update times for all objects in your application as
well as the last time your application completed a sync, it's time to implement
the manual recovery process. This example handles two main recovery
operations:
• Delete objects from the new realm that were previously deleted from the
backup realm
You can follow along with the implementation of these operations in the code
samples below.
Alternative Strategies
• Overwrite the entire backend with the backup state: With no "last
updated time" or "last synced time", upsert all objects from the backup
realm into the new realm. There is no way to recovered unsynced
deletions with this approach. This approach overwrites all data written
to the backend by other clients since the last sync. Recommended for
applications where only one user writes to each realm.
Partition-Based Sync is an older mode for using Atlas Device Sync with the
Realm React Native SDK. We recommend using Flexible Sync for new apps.
The information on this page is for users who are still using Partition-Based
Sync.
Partition Value
When you select Partition-Based Sync for your backend App configuration,
your client implementation must include a partition value. This is the value of
the partition key field you select when you configure Partition-Based Sync.
The partition value determines which data the client application can access.
You pass in the partition value when you open a synced realm.
<RealmProvider
schema={[YourObjectModel]}
sync={{
partitionValue: 'testPartition',
}}>
<RestOfApp />
</RealmProvider>
To copy data from an existing realm to a new realm with different configuration
options, pass the new configuration the Realm.writeCopyTo() method.
NOTE
If you write the copied realm to a realm file that already exists, the data is
written object by object. The copy operation replaces objects if there already
exists objects for given primary keys. The schemas of the realm you copy and
the realm you are writing to must be compatible for the copy operation to
succeed. Only objects in the schemas of both configurations are copied over.
EXAMPLE
TypeScript
JavaScript
const localConfig = {
schema: [Car],
path: "localOnly.realm",
};
const localRealm = await Realm.open(localConfig);
const syncedConfig = {
schema: [Car],
path: "copyLocalToSynced.realm",
sync: {
user: app.currentUser,
partitionValue: "myPartition",
},
};
localRealm.writeCopyTo(syncedConfig);
const syncedRealm = await Realm.open(syncedConfig);
You can also combine changes to configuration. For example, you can open a
local encrypted realm as a synced unencrypted realm or a unencrypted synced
realm as an encrypted synced realm.
EXAMPLE
TypeScript
JavaScript
// Create a secure key.
const encryptionKey = new Int8Array(64);
// ... store key
const syncedEncryptedConfig = {
schema: [Car],
path: "syncedEncrypted.realm",
sync: {
user: app.currentUser,
partitionValue: "myPartition",
},
encryptionKey,
};
const syncedEncryptedRealm = await Realm.open(syncedEncryptedConfig);
const localUnencryptedConfig = {
schema: [Car],
path: "copyLocalUnencrypted.realm",
};
syncedEncryptedRealm.writeCopyTo(localUnencryptedConfig);
const localUnencryptedRealm = await Realm.open(syncedEncryptedConfig);
You can migrate your App Services Device Sync Mode from Partition-Based
Sync to Flexible Sync. Migrating is an automatic process that does not require
any changes to your application code. Automatic migration requires Realm
Node.js SDK version 11.10.0 or newer.
Migrating enables you to keep your existing App Services users and
authentication configuration. Flexible Sync provides more versatile
permissions configuration options and more granular data synchronization.
For more information about how to migrate your App Services App from
Partition-Based Sync to Flexible Sync, refer to Migrate Device Sync Modes.
The automatic migration from Partition-Based Sync to Flexible Sync does not
require any changes to your client code. However, to support this
functionality, Realm automatically handles the differences between the two
Sync Modes by:
• Injecting a partitionKey field into every object if one does not already
exist. This is required for the automatic Flexible Sync subscription.
If you need to make updates to your client code after migration, consider
updating your client codebase to remove hidden migration functionality. You
might want update your client codebase when:
• You want to implement more fine-grained control over what data you
sync
Make these changes to convert your Partition-Based Sync client code to use
Flexible Sync:
This enables you to see all of your subscription logic together in your
codebase for future iteration and debugging.
WARNING
This page shows how to set a Sync client log level in Realm React Native SDK
versions 11.9.0 and earlier. Realm React Native SDK v12.0.0 supersedes this
logging implementation with a Realm logger you can set and configure for
your application. For information on how to set a Realm logger in a later
version, refer to Logging - React Native SDK.
EXAMPLE
In the following example, an application developer sets the sync client log
level to "debug".
Realm.App.Sync.setLogLevel(app, "debug");
TIP
You can use Data Ingest to stream data from the client application to a Flexible
Sync-enabled Atlas App Services App.
You might want to sync data unidirectionally in IoT applications, such as a
weather sensor sending data to the cloud. Data Ingest is also useful for writing
other types of immutable data where you do not require conflict resolution,
such as creating invoices from a retail app or logging application events.
TypeScript
JavaScript
class WeatherSensor extends Realm.Object{
static schema = {
name: 'WeatherSensor',
// sync WeatherSensor objects one way from your device
// to your Atlas database.
asymmetric: true,
primaryKey: '_id',
properties: {
_id: 'objectId',
deviceId: 'string',
temperatureInFahrenheit: 'int',
barometricPressureInHg: 'float',
windSpeedInMph: 'float',
},
};
}
function LogIn() {
const app = useApp();
useEffect(() => {
app.logIn(Realm.Credentials.anonymous());
}, []);
return <></>;
}
Data Ingest is a feature of Flexible Sync, so the App you connect to must
use Flexible Sync.
Open a Realm
After you have an authenticated user, you can open a synced realm using a
Flexible Sync configuration object.
TypeScript
JavaScript
// Create a configuration object
const realmConfig = { schema: [WeatherSensor] };
// Create a realm context
const {RealmProvider, useRealm, useObject, useQuery} =
createRealmContext(realmConfig);
// Expose a sync realm
function AppWrapperSync() {
return (
<AppProvider id={APP_ID}>
<UserProvider fallback={LogIn}>
<RealmProvider
sync={{
flexible: true,
onError: console.error
}}>
<App />
</RealmProvider>
</UserProvider>
</AppProvider>
);
}
Unlike bi-directional Sync, Data Ingest does not use a Flexible Sync
subscription.
Once you have an open Realm, you can create an asymmetric object inside a
write transaction using Realm.create(). When creating an asymmetric
object, Realm.create() returns undefined rather than the object itself.
You cannot read these objects. Once created, they sync to the App Services
backend and the linked Atlas database.
You can use Data Ingest to stream data from the client application to a Flexible
Sync-enabled Atlas App Services App.
TypeScript
JavaScript
class WeatherSensor extends Realm.Object{
static schema = {
name: 'WeatherSensor',
// sync WeatherSensor objects one way from your device
// to your Atlas database.
asymmetric: true,
primaryKey: '_id',
properties: {
_id: 'objectId',
deviceId: 'string',
temperatureInFahrenheit: 'int',
barometricPressureInHg: 'float',
windSpeedInMph: 'float',
},
};
}
For more information on how to define an asymmetric object, refer to Define
an Asymmetric Object.
To stream data from the client to your backend App, you must connect to an
App Services backend and authenticate a user.
function LogIn() {
const app = useApp();
useEffect(() => {
app.logIn(Realm.Credentials.anonymous());
}, []);
return <></>;
}
Data Ingest is a feature of Flexible Sync, so the App you connect to must
use Flexible Sync.
Open a Realm
After you have an authenticated user, you can open a synced realm using a
Flexible Sync configuration object.
TypeScript
JavaScript
// Create a configuration object
const realmConfig = { schema: [WeatherSensor] };
// Create a realm context
const {RealmProvider, useRealm, useObject, useQuery} =
createRealmContext(realmConfig);
// Expose a sync realm
function AppWrapperSync() {
return (
<AppProvider id={APP_ID}>
<UserProvider fallback={LogIn}>
<RealmProvider
sync={{
flexible: true,
onError: console.error
}}>
<App />
</RealmProvider>
</UserProvider>
</AppProvider>
);
}
Unlike bi-directional Sync, Data Ingest does not use a Flexible Sync
subscription.
Once you have an open Realm, you can create an asymmetric object inside a
write transaction using Realm.create(). When creating an asymmetric
object, Realm.create() returns undefined rather than the object itself.
You cannot read these objects. Once created, they sync to the App Services
backend and the linked Atlas database.
Atlas Device Sync completely manages the lifecycle of this data. It is
maintained on the device until Data Ingest synchronization is complete, and
then removed from the device.
You can use Data Ingest to stream data from the client application to a Flexible
Sync-enabled Atlas App Services App.
TypeScript
JavaScript
class WeatherSensor extends Realm.Object{
static schema = {
name: 'WeatherSensor',
// sync WeatherSensor objects one way from your device
// to your Atlas database.
asymmetric: true,
primaryKey: '_id',
properties: {
_id: 'objectId',
deviceId: 'string',
temperatureInFahrenheit: 'int',
barometricPressureInHg: 'float',
windSpeedInMph: 'float',
},
};
}
To stream data from the client to your backend App, you must connect to an
App Services backend and authenticate a user.
function LogIn() {
const app = useApp();
useEffect(() => {
app.logIn(Realm.Credentials.anonymous());
}, []);
return <></>;
}
Data Ingest is a feature of Flexible Sync, so the App you connect to must
use Flexible Sync.
Open a Realm
After you have an authenticated user, you can open a synced realm using a
Flexible Sync configuration object.
TypeScript
JavaScript
// Create a configuration object
const realmConfig = { schema: [WeatherSensor] };
// Create a realm context
const {RealmProvider, useRealm, useObject, useQuery} =
createRealmContext(realmConfig);
// Expose a sync realm
function AppWrapperSync() {
return (
<AppProvider id={APP_ID}>
<UserProvider fallback={LogIn}>
<RealmProvider
sync={{
flexible: true,
onError: console.error
}}>
<App />
</RealmProvider>
</UserProvider>
</AppProvider>
);
}
Unlike bi-directional Sync, Data Ingest does not use a Flexible Sync
subscription.
Once you have an open Realm, you can create an asymmetric object inside a
write transaction using Realm.create(). When creating an asymmetric
object, Realm.create() returns undefined rather than the object itself.
You cannot read these objects. Once created, they sync to the App Services
backend and the linked Atlas database.
You can use Data Ingest to stream data from the client application to a Flexible
Sync-enabled Atlas App Services App.
TypeScript
JavaScript
class WeatherSensor extends Realm.Object{
static schema = {
name: 'WeatherSensor',
// sync WeatherSensor objects one way from your device
// to your Atlas database.
asymmetric: true,
primaryKey: '_id',
properties: {
_id: 'objectId',
deviceId: 'string',
temperatureInFahrenheit: 'int',
barometricPressureInHg: 'float',
windSpeedInMph: 'float',
},
};
}
To stream data from the client to your backend App, you must connect to an
App Services backend and authenticate a user.
function LogIn() {
const app = useApp();
useEffect(() => {
app.logIn(Realm.Credentials.anonymous());
}, []);
return <></>;
}
Data Ingest is a feature of Flexible Sync, so the App you connect to must
use Flexible Sync.
Open a Realm
After you have an authenticated user, you can open a synced realm using a
Flexible Sync configuration object.
TypeScript
JavaScript
// Create a configuration object
const realmConfig = { schema: [WeatherSensor] };
// Create a realm context
const {RealmProvider, useRealm, useObject, useQuery} =
createRealmContext(realmConfig);
// Expose a sync realm
function AppWrapperSync() {
return (
<AppProvider id={APP_ID}>
<UserProvider fallback={LogIn}>
<RealmProvider
sync={{
flexible: true,
onError: console.error
}}>
<App />
</RealmProvider>
</UserProvider>
</AppProvider>
);
}
Unlike bi-directional Sync, Data Ingest does not use a Flexible Sync
subscription.
Once you have an open Realm, you can create an asymmetric object inside a
write transaction using Realm.create(). When creating an asymmetric
object, Realm.create() returns undefined rather than the object itself.
You cannot read these objects. Once created, they sync to the App Services
backend and the linked Atlas database.
You can test the Realm React Native SDK with popular React Native testing
libraries like Jest, Jasmine, and Mocha.
TIP
See also:
Official React Native Testing Documentation
Clean Up Tests
When testing the Realm React Native SDK, you must close realms
with Realm.close() after you're done with them to prevent memory leaks.
You should also delete the realm file with Realm.deleteFile() during clean up to
keep your tests idempotent.
The below example uses the Jest testing framework. It uses Jest's built-
in beforeEach() and afterEach() hooks for test set up and tear down,
respectively.
const config = {
schema: [Car],
};
let realm;
beforeEach(async () => {
realm = await Realm.open(config);
});
afterEach(() => {
if (!realm.isClosed) {
realm.close();
}
if (config) {
Realm.deleteFile(config);
}
});
test("Close a Realm", async () => {
expect(realm.isClosed).toBe(false);
realm.close();
expect(realm.isClosed).toBe(true);
});
Requirements
Setup
1
Follow the Getting Started with Flipper guide to install Flipper for your
operating system.
If you did not use react-native init to initialize your app, follow the
manual setup guide(s) to add Flipper to your app:
Launch your React Native mobile app and open the Flipper Desktop
application. Your React Native app should be available in Flipper's App
Inspect tab.
Within the Flipper Desktop App, click the Plugin Manager tab on the left-hand
side. When the Plugin Manager modal opens, select the Install Plugins option.
Search for the "realm-flipper-plugin" and install it.
Then, click the red alert to reload the Flipper Desktop app and apply changes.
Realm Flipper Plugin Device enables React Native applications to interact with
Realm Flipper Plugin. Realm Flipper Plugin Device also requires the React
Native Flipper plugin. To install both, run the following command:
cd ios
pod install
cd ..
6
return (
<View>
<RealmPlugin realms={[realm]} /> {/* pass in the Realm to the plugin*/}
// ...
</View>
);
Navigate back to the Realm Flipper Desktop app. Under the Enabled plugins,
you should see the Realm plugin.
If you don't see the Realm plugin enabled, click the Disabled dropdown and
enable the Realm Flipper Plugin by tapping the + Enable Plugin button.
You can set or change your app's log level to develop or debug your
application. You might want to change the log level to log different amounts of
data depending on the app's environment. You can specify different log levels
or custom loggers.
TIP
See also:
This page shows how to set a Realm logger, which was added in Realm React
Native SDK v12.0.0. This supersedes setting the Sync client log level in earlier
versions of the Realm React Native SDK. For information on how to set the
Sync client log level in an earlier version, refer to Set the Client Log Level -
React Native SDK.
You can set the level of detail reported by the Realm React Native SDK. To
configure the log level, pass a valid level string value to setLogLevel():
• "all"
• "trace"
• "debug"
• "detail"
• "info"
• "warn"
• "error"
• "fatal"
• "off"
Realm.setLogLevel('trace');
Realm.setLogLevel('off');
You must use setLogger() before you open a realm with RealmProvider.
You can't use setLogger() in a hook in the same component
as RealmProvider because RealmProvider opens a realm when it is
mounted. Hooks generally run after a component is mounted, which
means RealmProvider already opened a realm.
Most of the time, you should set your custom logger outside of the React tree.
For example, in your app's root index.js file.
This sets the logging behavior for all Realm logging in your application,
regardless of where you set it. If you do not provide a log level, the default is
"info".
MongoDB collects anonymized telemetry data from the Realm SDKs to better
understand how and where developers use Realm. This data helps us
determine what to work on next and lets us gracefully deprecate features with
minimal impact. None of the telemetry data collected by the Realm SDKs
personally identifies you, your app, or your employer.
Data is collected whenever you install the SDK, build your app (if applicable),
or run your app in a non-production, debugging environment.
Telemetry is on by default for the Realm SDKs. You can disable telemetry at
any time b by setting the REALM_DISABLE_ANALYTICS environment variable
to true in your shell environment:
export
REALM_DISABLE_ANALYTICS=true
TIP
See also:
When you select Flexible Sync for your backend App configuration, your client
implementation must include subscriptions to queries on queryable fields.
Flexible Sync works by synchronizing data that matches query subscriptions
you maintain in the client application.
NOTE
Flexible Sync does not support all the query operators available in Realm
Query Language and the SDK's query engine. See Flexible Sync RQL
Limitations for details.
Subscription sets are based on a specific type of Realm object. You might
have multiple subscriptions if you have many types of Realm objects.
To use Flexible Sync in your client application, open a synced realm with a
Flexible Sync configuration. Then, manage subscriptions to determine which
documents to sync.
TIP
See also:
TIP
Device Sync supports two Sync Modes: Flexible Sync, and the older Partition-
Based Sync. If your App Services backend uses Partition-Based Sync, refer
to Partition-Based Sync - React Native SDK.
For more information about synced realms, including directions on how to set
up sync in an App Services App, refer to Atlas Device Sync Overview.
Prerequisites
Before you configure a realm with Flexible Sync in a React Native application:
1. Enable Flexible Sync on the backend. You must configure Flexible Sync
in the backend before you can use it with your client application.
By default, Realm syncs all data from the server before returning anything. If
you want to sync data in the background, read Configure a Synced Realm
While Offline.
2. Configure AppProvider.
NOTE
Partition-Based Sync
This page covers Flexible Sync realms. Flexible Sync is the preferred mode for
new apps that use Atlas Device Sync. For information about realms using the
older Partition-Based Sync, refer to Partition-Based Sync.
AppProvider
To set up your App client, pass the App ID string to the id prop
of AppProvider. Wrap any components that need to access the App with
the AppProvider.
You can find more information about AppProvider on the Connect To Atlas
App Services page.
UserProvider
RealmProvider
When RealmProvider is rendered, it opens the realm. This means that the
provider renders successfully or its child components can't access the realm.
2. Configure AppProvider.
<RealmProvider
schema={[YourObjectModel]}
sync={{
flexible: true,
initialSubscriptions: {
update(subs, realm) {
subs.add(realm.objects(YourObjectModel));
},
},
}}>
<RestOfApp />
</RealmProvider>
</UserProvider>
</AppProvider>
);
}
For more information about configuring and using RealmProvider, check out
the Configure a Realm page.
Configuration Options
You can configure RealmProvider by setting props that match the properties
of a Configuration object. You can also set fallback and realmRef props.
• realmRef
Used with useRef to expose the configured realm to processes outside
of RealmProvider. This can be useful for things like a client reset fallback.
• fallback
Rendered while waiting for the realm to open. Local realms usually open fast
enough that the fallback prop isn't needed.
TypeScript
JavaScript
import React from 'react';
import {AppProvider, UserProvider, RealmProvider} from '@realm/react';
function AppWrapperSync({customBaseFilePath}) {
return (
<UserProvider fallback={LogIn}>
<RealmProvider
path={customRealmPath}
schema={[Profile]}
sync={{
flexible: true,
}}>
<RestOfApp />
</RealmProvider>
</UserProvider>
</AppProvider>
);
}
Where, exactly, your Realm file is stored can vary depending on how you
set Realm.BaseConfiguration.path:
You can open a realm immediately with background sync or after a timeout.
NOTE
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 may want to sync changes in the background to display partial data to the
user while the synced realm downloads data from the server, preventing the
user experience from being blocked. We recommend syncing changes in the
background for applications in which the user's device may go offline. To sync
changes in the background, open a synced realm synchronously.
TypeScript
JavaScript
import React from 'react';
import {AppProvider, UserProvider, RealmProvider} from '@realm/react';
function AppWrapperOfflineSync() {
const realmAccessBehavior = {
type: 'openImmediately',
};
return (
<AppProvider id={APP_ID}>
<UserProvider fallback={LogIn}>
<RealmProvider
schema={[Profile]}
sync={{
flexible: true,
newRealmFileBehavior: realmAccessBehavior,
existingRealmFileBehavior: realmAccessBehavior,
}}>
<RestOfApp />
</RealmProvider>
</UserProvider>
</AppProvider>
);
}
If you want to sync data but you're in an environment where it's uncertain if the
user has an Internet connection, specify a timeOut. This automatically opens
the realm when either:
If the realm doesn't finish downloading before the timeout, the initial Sync
continues in the background.
TypeScript
JavaScript
import React from 'react';
import {AppProvider, UserProvider, RealmProvider} from '@realm/react';
function AppWrapperTimeoutSync() {
const realmAccessBehavior = {
type: 'downloadBeforeOpen',
timeOutBehavior: 'openLocalRealm',
timeOut: 1000,
};
return (
<AppProvider id={APP_ID}>
<UserProvider fallback={LogIn}>
<RealmProvider
schema={[Profile]}
sync={{
flexible: true,
newRealmFileBehavior: realmAccessBehavior,
existingRealmFileBehavior: realmAccessBehavior,
}}>
<RestOfApp />
</RealmProvider>
</UserProvider>
</AppProvider>
);
}
@realm/react has providers and hooks that simplify working with your
synced realm and its data.
AppProvider
Provider/Hook Description Example
UserProvider
UserProvider React component that provides a Realm user for the See UserProvider
Sync hooks. A UserProvider is required for an app to use
Sync hooks.
useUser Accesses the currently-authenticated Realm user from const user = useUse
the UserProvider context. The user is stored as React
state and will trigger a re-render whenever it changes.
RealmProvider
Provider/Hook Description Example
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.
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.
Prerequisites
You need to meet the following requirements before you can use Atlas Device
Sync with the React Native SDK:
Before you can add Flexible Sync subscriptions to a React Native client, you
must:
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.
Subscribe to Queries
For all subscriptions, you need an authenticated user and a Flexible Sync
realm.
Subscribe to a Query
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.
Most of the time, you should give your subscriptions a name. If you don't, the
name is set to null.
API Reference
• subscribe()
• SubscriptionOptions
• null
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. You can specify
different behavior for your subscriptions and how they handle writing for
downloads.
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.
You can optionally specify a timeout value to limit how long the sync
download runs:
API Reference
• waitForSync
Unsubscribe from a Query
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.
• unsubscribe()
You can use the Subscriptions API to manually manage a set of subscriptions
to specific queries on queryable fields.
• Add subscriptions
• Remove 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.
API Reference
• SubscriptionSet
Add a Subscription
• Completed tasks
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.
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
• FlexibleSyncConfiguration
You can check the subscription state to see if the server has acknowledged
the subscription and the device has downloaded the data locally.
• 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>
);
}
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 Realm SDK does not provide a way to check whether all documents that
match a subscription have synced to the device.
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
API Reference
• MutableSubscriptionSet.add()
• SubScriptionSet.update()
• SubscriptionOptions.throwOnUpdate
Remove Subscriptions
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:
When you remove a subscription query, the server also removes synced data
from the client device.
realm.subscriptions.update((mutableSubs) => {
// remove a subscription with a specific query
mutableSubs.remove(tasks.filtered('owner == "Ben"'));
});
realm.subscriptions.update((mutableSubs) => {
// remove a subscription with a specific name
mutableSubs.removeByName("longRunningTasksSubscription");
});
let subscriptionReference;
realm.subscriptions.update((mutableSubs) => {
subscriptionReference = mutableSubs.add(realm.objects("Task"));
});
// later..
realm.subscriptions.removeSubscription(subscriptionReference);
realm.subscriptions.update((mutableSubs) => {
mutableSubs.removeByObjectType("Team");
});
You can remove all unnamed subscriptions from the subscription set by
calling .removeUnnamed() on mutableSubs. .removeUnnamed() returns the
number of unnamed subscriptions removed.
API Reference
• removeUnnamed()
realm.subscriptions.update((mutableSubs) => {
mutableSubs.removeAll();
});
Performance Considerations
API Efficiency
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 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.
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.
List Queries
You can query a list of constants to see if it contains the value of a queryable
field:
If a queryable field has an array value, you can query to see if it contains a
constant value:
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"
This page explains how to manually recover unsynced realm data after a client
reset using the Manual Recovery client reset mode.
For more information about the other available client reset modes, refer
to Reset a Client Realm.
The specifics of manual recovery depend heavily upon your application and
your schema. However, there are a few techniques that can help with manual
recoveries. The Track Changes by Object Strategy section demonstrates one
method of recovering unsynced changes during a client reset.
WARNING
Do not expect to recover all unsynced data after a breaking schema change.
The best way to preserve user data is to never make a breaking schema
change at all.
IMPORTANT
In this strategy, you add a "Last Updated Time" to each object model to track
when each object last changed. We'll watch the to determine when the realm
last uploaded its state to the backend.
When backend invokes a client reset, find objects that were deleted, created,
or updated since the last sync with the backend. Then copy that data from the
backup realm to the new realm.
1. Client reset error: Your application receives a client reset error code
from the backend.
3. Close all instances of the realm: Close all open instances of the realm
experiencing the client reset. If your application architecture makes this
difficult (for instance, if your app uses many realm instances
simultaneously in listeners throughout the application), it may be easier
to restart the application. You can do this programmatically or through a
direct request to the user in a dialog.
5. Open new instance of the realm: Open a new instance of the realm using
your typical sync configuration. If your application uses multiple realms,
you can identify the realm experiencing a client reset from the backup
file name.
6. Download all realm data from the backend: Download the entire set of
data in the realm before you proceed. This is the default behavior of
the FlexibleSyncConfiguration object.
7. Open the realm backup: Use the error.config object passed as an
argument to the SyncConfiguration.error callback function.
8. Migrate unsynced changes: Query the backup realm for data to recover.
Insert, delete or update data in the new realm accordingly.
Example
NOTE
• This example keeps track of the last time each object was updated. As a
result, the recovery operation overwrites the entire object in the new
realm if any field was updated after the last successful sync of the
backup realm. This could overwrite fields updated by other clients with
old data from this client. If your realm objects contain multiple fields
containing important data, consider keeping track of the last updated
time of each field instead, and recovering each field individually.
For more information on other ways to perform a manual client reset with data
recovery, refer to the Alternative Strategies section.
1
Add a new property to your Realm object schema to track the last time it was
updated. Whenever you create or update a Realm object with the schema,
include a timestamp with the update time.
Ordinarily, there is no way to detect when a Realm object was last modified.
This makes it difficult to determine which changes were synced to the
backend. By adding a timestamp lastUpdated to your Realm object models
and updating that timestamp to the current time whenever a change occurs,
you can keep track of when objects were changed.
const DogSchema = {
name: "Dog",
properties: {
name: "string",
age: "int?",
lastUpdated: "int",
},
};
2
const config = {
schema: [DogSchema],
sync: {
user: app.currentUser,
partitionValue: "MyPartitionValue",
clientReset: {
mode: "manual",
},
error: handleSyncError, // callback function defined later
},
};
3
Just knowing when objects were changed isn't enough to recover data during
a client reset. You also need to know when the realm last completed a sync
successfully. This example implementation uses a singleton object in a
separate realm called LastSynced paired with a change listener to record
when a realm finishes syncing successfully.
Define your LastSynced Realm to track the latest time your realm
synchronizes.
const LastSyncedSchema = {
name: "LastSynced",
properties: {
realmTracked: "string",
timestamp: "int?",
},
primaryKey: "realmTracked",
};
const lastSyncedConfig = { schema: [LastSyncedSchema] };
const lastSyncedRealm = await Realm.open(lastSyncedConfig);
lastSyncedRealm.write(() => {
lastSyncedRealm.create("LastSynced", {
realmTracked: "Dog",
});
});
Now that you've recorded update times for all objects in your application as
well as the last time your application completed a sync, it's time to implement
the manual recovery process. This example handles two main recovery
operations:
• Restore unsynced inserts and updates from the backup realm
• Delete objects from the new realm that were previously deleted from the
backup realm
You can follow along with the implementation of these operations in the code
samples below.
• Overwrite the entire backend with the backup state: With no "last
updated time" or "last synced time", upsert all objects from the backup
realm into the new realm. There is no way to recovered unsynced
deletions with this approach. This approach overwrites all data written
to the backend by other clients since the last sync. Recommended for
applications where only one user writes to each realm.
TypeScript
JavaScript
class WeatherSensor extends Realm.Object{
static schema = {
name: 'WeatherSensor',
// sync WeatherSensor objects one way from your device
// to your Atlas database.
asymmetric: true,
primaryKey: '_id',
properties: {
_id: 'objectId',
deviceId: 'string',
temperatureInFahrenheit: 'int',
barometricPressureInHg: 'float',
windSpeedInMph: 'float',
},
};
}
2
Connect and Authenticate with an App Services App
To stream data from the client to your backend App, you must connect to an
App Services backend and authenticate a user.
function LogIn() {
const app = useApp();
useEffect(() => {
app.logIn(Realm.Credentials.anonymous());
}, []);
return <></>;
}
Data Ingest is a feature of Flexible Sync, so the App you connect to must
use Flexible Sync.
Open a Realm
After you have an authenticated user, you can open a synced realm using a
Flexible Sync configuration object.
TypeScript
JavaScript
// Create a configuration object
const realmConfig = { schema: [WeatherSensor] };
// Create a realm context
const {RealmProvider, useRealm, useObject, useQuery} =
createRealmContext(realmConfig);
// Expose a sync realm
function AppWrapperSync() {
return (
<AppProvider id={APP_ID}>
<UserProvider fallback={LogIn}>
<RealmProvider
sync={{
flexible: true,
onError: console.error
}}>
<App />
</RealmProvider>
</UserProvider>
</AppProvider>
);
}
Unlike bi-directional Sync, Data Ingest does not use a Flexible Sync
subscription.
Once you have an open Realm, you can create an asymmetric object inside a
write transaction using Realm.create(). When creating an asymmetric
object, Realm.create() returns undefined rather than the object itself.
You cannot read these objects. Once created, they sync to the App Services
backend and the linked Atlas database.