Docs Menu
Docs Home
/ /
Atlas App Services

Tutorial: Atlas Device Sync for React Native

On this page

  • Prerequisites
  • Start with the Template App
  • Set Up the Template App
  • Install Dependencies
  • Build the App
  • Test the App
  • Get Familiar With the Template App
  • Atlas App Services App
  • React Native App
  • Add a Priority Level Field
  • Define the Priority Levels
  • Update the Item Data Model
  • Add a Priority Picker
  • Run and Test the App
  • Update the Sync Subscription
  • Add a Mode Toggle to the UI
  • Update the Sync Subscription
  • Test the App
  • What's Next?

Estimated time to complete: 30 minutes, depending on your experience with React Native

You can use the Realm React Native SDK and @realm/react to build a mobile application with React Native. This tutorial walks you through how to build your own app that uses Flexible Sync.

For this tutorial, we'll start with a pre-built TypeScript template application to see how everything fits together.

The app is a pre-built template that includes a working React Native application (frontend) and its corresponding App Services App configuration files (backend).

The template app is a basic to-do list application that lets users do various things to manage their tasks:

  • Create email/password accounts and log in and out of the app.

  • Create, read, update, and delete their own tasks.

  • View all tasks, even if the user is not the owner.

After you've got the template app running, you will add a new priority field to the existing Item model and update the Flexible Sync subscription to only show items within a range of priorities. This example illustrates how you might adapt the template app for your own needs. You would not necessarily make this change given the current structure of the template app.

The template app provides a toggle that simulates the device being in an offline mode. This toggle lets you quickly test Device Sync functionality, emulating the user having no internet connection. However, you would likely remove this toggle in a production application.

Note

Check Out the Quick Start

If you prefer to explore on your own rather than follow a guided tutorial, check out the React Native Quick Start. It includes copyable code examples and the essential information that you need to set up a React Native app with Atlas Device Sync.

  • You must set up your local environment for React Native development before you can begin this tutorial. For detailed instructions, see Setting up the development environment in the React Native docs.

  • This tutorial starts with a Template App. You need an Atlas Account, an API key, and App Services CLI to create a Template App.

    • You can learn more about creating an Atlas account in the Atlas Getting Started documentation. For this tutorial, you need an Atlas account with a free-tier cluster.

    • You also need an Atlas API key for the MongoDB Cloud account you wish to log in with. You must be a Project Owner to create a Template App using App Services CLI.

    • To learn more about installing App Services CLI, see Install App Services CLI. After installing, run the login command using the API key for your Atlas project.

This tutorial is based on the React Native SDK Flexible Sync Template App named react-native.todo.flex. We start with the default app and build new features on it.

To learn more about the Template Apps, see Template Apps.

If you don't already have an Atlas account, sign-up to deploy a Template App.

Follow the procedure described in the Create an App Services App guide, and select Create App from Template. Select the Real-time Sync template. This creates an App Services App pre-configured to use with one of the Device Sync template app clients.

After you create a template app, the UI displays a modal labeled Get the Front-end Code for your Template. This modal provides instructions for downloading the template app client code as a .zip file or using App Services CLI to get the client.

After selecting the .zip or App Services CLI method, follow the on-screen instructions to get the client code. For this tutorial, select the JavaScript (React Native) client code.

Note

The default Windows ZIP utility may show the .zip file as empty. If you encounter this, use one of the third-party zip programs that are available.

The appservices apps create command sets up the backend and creates a React Native template app for you to use as a base for this tutorial.

Run the following command in a terminal window to create an app named "MyTutorialApp" that is deployed in the US-VA region with its environment set to "development" (instead of production or QA).

appservices app create \
--name MyTutorialApp \
--template react-native.todo.flex \
--deployment-model global \
--environment development

The command creates a new directory in your current path with the same name as the value of the --name flag.

You can fork and clone a GitHub repository that contains the Device Sync client code. The React Native client code is available at https://github.com/mongodb/template-app-react-native-todo.

If you use this process to get the client code, you must create a template app to use with the client. Follow the instructions at Create a Template App to use the Atlas App Services UI, App Services CLI, or Admin API to create a Device Sync template app.

Use the following steps to get the template app up and running on your computer:

1

In your terminal, go to the directory that contains the client code. If you created the app with the App Services CLI, go to MyTutorialApp/react-native.todo.flex. Otherwise, go to the root of your downloaded or cloned project. Then run following commands to navigate to install the app's dependencies:

npm install

To build and run the app on an iOS device or simulator, install the additional iOS dependencies with CocoaPods.

cd ios
npx pod-install
2

At this point, you should have a fully functional React Native app that can run on iOS, Android, or both.

If you encounter an error or otherwise have issues, make sure that your React Native environment is set up correctly. Refer to the official React Native development environment setup guide. Follow all the steps for your Development OS and Target OS.

To make sure that everything works on iOS, build the app and run it in an iOS simulator:

npm run ios

To make sure that everything works on Android:

  1. Start an Android emulator. See Run apps on the Android Emulator for details on how to do this.

  2. Build the app on the emulator:

    npm run android

    One common error is Error: spawn ./gradlew EACCES. This means the project file permissions aren't what they need to be. On MacOS, you can fix this by entering chmod 755 android/gradlew in your terminal.

3

When the build completes, you should have a functional app running on your simulator. In the app, register a new account and test the features:

  • Add a few to-do items to the list.

  • Press the checkbox on one or two items to mark them as complete.

  • Press the X on an item to delete it from the list.

  • Toggle internet connectivity in the app to simulate offline mode.

If you connect to your Atlas Cluster and query the todo.Item collection you can see your App's data. As long as the React Native app is not in offline mode, new data and changes in the app automatically sync to the todo.Item collection.

Tip

To learn how to connect to your Atlas Cluster, see Connect to a Cluster.

Similarly, any changes in the collection automatically sync down to the React Native app. Try changing an item's completion status in your cluster - the React Native app will automatically update with the new value whenever a network connection is available.

Tip

To learn more about updating data in your cluster, see Update Documents.

Now that you have the template app running let's dive into the code to see what we're working with.

The template app includes a fully configured App Services App in the backend directory. It has a unique appId value in atlasConfig.json that client applications use to connect.

It also includes the following pre-defined configurations:

  • A data source linked to your Atlas Cluster.

  • A data model for the todo.Item collection that matches the Item class in the React Native app.

  • An authentication provider that lets users register for and log in to your app with an email and password.

  • A flexible sync configuration with a single session role that allows users to read and write their own items and view other users' items.

The React Native app is a fully-configured mobile client that can run on iOS and Android devices.

The app uses the @realm/react library. The library includes React hooks and components that streamline working with your Atlas backend and Realm database.

The app contains some configuration files and directories, but you can ignore those unless you want to customize the app. For this tutorial, you should be familiar with the React components in the source/ directory:

File Name
Description
ItemSchema.tsx
The Item class, including its object data model. We import this class in AppWrapper.tsx to include it in the app's overall Realm schema.
AppWrapper.tsx
This is the root component for the app. It functions as a wrapper component and contains all of the @realm/react providers. This is where you configure your realm and your connection to your Atlas backend.
App.tsx
Most of the app's functionality is contained in this component and its children. Because the @realm/react providers are wrapped around this component, it can access an instance of your Atlas backend, user objects, and interact with the Realm database.
WelcomeView.tsx
The user registration and login form that users see when they first open the app.
ItemListView.tsx
The main to-do list app that users interact with after they log in. It queries for Item Realm objects and displays them in a list. It also includes the code to create new Item objects and store them in Realm.
CreateToDoPrompt.tsx
A UI form that lets us enter data for new Item objects. The code that actually creates new objects is in ItemListView.tsx.
LogoutButton.tsx
A reusable button that logs out an authenticated user.
OfflineModeButton.tsx
A reusable button that simulates an offline mode by pausing and resuming the current Realm syncSession.

Now that you're more familiar with what's already provided in the template app, let's write some code to implement a new feature.

For this tutorial, we'll add a new priority property to the Item objects. This will let us organize to-dos by how important they are and allow us to focus only on the most important ones.

1

We want to allow a small number of named priority levels, and we want to easily be able sort the levels. To do this, we'll use a helper function to define an enum object that maps a set of ordered level names to and from an integer that represents their priority.

Add the following code directly under the import statements in source/ItemSchema.tsx:

function createEnum(arr) {
arr.forEach((p, i) => arr[p] = i);
return arr;
}
// Priority.High === 1
// Priority[Priority.High] === "High"
export const Priority = createEnum([
"Severe",
"High",
"Medium",
"Low",
])

The priority levels in the enum are ordered from most important to least. The corresponding index value for each level increases from the most important, Priority[0], to the least important, Priority[3]. This means that a higher priority level (meaning more important) has a lower index value.

2

Now we have an enum that defines the possible values of the priority field. However, we still have to define the priority field in the Item class.

Add the following lines to your code in source/ItemSchema.tsx to add priority to the Item data model:

source/ItemSchema.tsx
export class Item extends Realm.Object<Item> {
_id!: BSON.ObjectId;
isComplete!: boolean;
summary!: string;
owner_id!: string;
priority!: string;
static schema: Realm.ObjectSchema = {
name: 'Item',
primaryKey: '_id',
properties: {
// This allows us to automatically generate a unique _id for each Item
_id: {type: 'objectId', default: () => new BSON.ObjectId()},
// All todo items will default to incomplete
isComplete: {type: 'bool', default: false},
summary: 'string',
owner_id: 'string',
priority: {
// Store the index value of the Priority enum rather than the name
type: 'int',
default: Priority.High
},
},
};
}

Note

Why Didn't This Break Sync

At this point, your React Native Item model and its corresponding schema in your App Services App no longer agree. That's okay!

Adding a property to a Realm object is not a breaking change and therefore does not require a client reset. The template app has Development Mode enabled, so changes to the client Realm object are reflected in the server-side schema. For more information, see Development Mode and Update Your Data Model.

3

Your app's data model now includes a priority for each Item object. Let's update the app UI so that you can choose a priority value when you add a new to-do to the list.

First, we'll install an external library to implement the priority picker component. Run the following in your terminal inside of your project root:

npm install @react-native-picker/picker

If you're building for iOS, make sure to link the associated Cocoapods after you've installed the package:

npx pod-install

Tip

You may need to rebuild your app after installing. To do so, stop the bundler for your project and then run the build command:

npm run ios
npm run android

Now that the package is fully installed, let's update the new to-do creation prompt component to use the picker.

Add the following imports to the top of source/CreateToDoPrompt.tsx:

import {Picker} from '@react-native-picker/picker';
import {Priority} from './ItemSchema';

Then, modify the CreateToDoPrompt component:

  • Add priority to the onSubmit() props definition

  • Keep track of priority in a state hook

  • Connect the state to the Picker component that you imported

  • Pass priority to the onSubmit() handler

source/CreateToDoPrompt.tsx
type Props = {
onSubmit(args: {summary: string; priority: string;}): void;
};
export function CreateToDoPrompt(props: Props): React.ReactElement<Props> {
const {onSubmit} = props;
const [summary, setSummary] = useState('');
const [priority, setPriority] = useState(Priority.High);
return (
<View style={styles.modalWrapper}>
<Text h4 style={styles.addItemTitle}>
Add To-Do Item
</Text>
<Input
placeholder="What do you want to do?"
onChangeText={(text: string) => setSummary(text)}
autoCompleteType={undefined}
/>
<Picker
style={{width: '80%'}}
selectedValue={priority}
onValueChange={value => setPriority(value)}>
{Priority.map(priority => (
<Picker.Item
key={priority}
label={priority}
value={Priority[priority]}
/>
))}
</Picker>
<Button
title="Save"
buttonStyle={styles.saveButton}
onPress={() => onSubmit({summary, priority})}
/>
</View>
);
}

In source/ItemListView.tsx, modify the createItem() function to accept and use priority:

source/ItemListView.tsx
const createItem = useCallback(
({summary, priority}: {summary: string, priority: string}) => {
realm.write(() => {
return new Item(realm, {
summary,
owner_id: user?.id,
priority
});
});
},
[realm, user],
);

Then, modify the create to-do submission handler to accept the priority level and pass it to createItem():

source/ItemListView.tsx
<CreateToDoPrompt
onSubmit={({summary, priority}) => {
setShowNewItemOverlay(false);
createItem({summary, priority});
}}
/>

Finally, modify the list item template to render the to-do's priority before the summary:

source/ItemListView.tsx
<ListItem
key={`${item._id}`}
bottomDivider
topDivider
hasTVPreferredFocus={undefined}
tvParallaxProperties={undefined}>
<Text>{item.priority}</Text>
<ListItem.Title style={styles.itemTitle}>
{item.summary}
</ListItem.Title>
<ListItem.Subtitle style={styles.itemSubtitle}>
{item.owner_id === user?.id ? '(mine)' : ''}
</ListItem.Subtitle>
<ListItem.CheckBox
checked={item.isComplete}
checkedColor={COLORS.primary}
iconType="material"
checkedIcon="check-box"
uncheckedIcon="check-box-outline-blank"
onPress={() => toggleItemIsComplete(item._id)}
/>
<Button
type="clear"
onPress={() => deleteItem(item._id)}
icon={
<Icon
type="material"
name="clear"
size={12}
color="#979797"
tvParallaxProperties={undefined}
/>
}
/>
</ListItem>
4

Your app should now allow users to set a priority level for new to-do items.

Rebuild the app and open it. Add some new to-do items to confirm that you can choose a priority level and that the list displays each to-do's priority.

The Device Sync protocol uses a flexible model where each sync client uses a standard RQL query to choose a subset of application data and then subscribes to the subset. This automatically pulls the latest version of all data in the subset to the device and syncs changes to the data between devices.

For example, the template app you're using has the following built-in subscription to items that the current user owns:

source/ItemListView.tsx
realm.subscriptions.update(mutableSubs => {
mutableSubs.removeByName(itemSubscriptionName);
mutableSubs.add(
realm.objects(Item).filtered(`owner_id == "${user?.id}"`),
{name: ownItemsSubscriptionName},
);
});

You can customize the subscription during runtime to sync only the data that your app needs. Let's add a feature to demonstrate how.

For this tutorial, we'll add a button that lets us toggle between two modes: one where the app syncs all to-do items and another where it only syncs important ones with a priority of High or Severe.

1

First, add a useState() hook to the ItemListView component to keep track of the current mode:

ItemListView.tsx
const [showImportantOnly, setShowImportantOnly] = useState(false);

Then, add a new button that toggles the mode to the bottom of the to-do list, after <ListItem>:

source/ItemListView.tsx
<Button
title={showImportantOnly ? 'Show All' : 'Show Important Only'}
buttonStyle={{
...styles.addToDoButton,
backgroundColor: showImportantOnly ? '#00A35C' : '#FFC010',
}}
onPress={() => setShowImportantOnly(showImportantOnly => !showImportantOnly)}
/>
2

At this point, the app can switch modes in the UI, but we haven't done anything else so the modes are functionally identical. Let's update the sync subscription to only sync data relevant to the current mode.

In the first useEffect of the ItemListView component, add code that checks the current mode and appends an additional priority filter to the query if the showImportantOnly mode is active:

source/ItemListView.tsx
useEffect(() => {
if (showAllItems) {
realm.subscriptions.update(mutableSubs => {
mutableSubs.removeByName(ownItemsSubscriptionName);
mutableSubs.add(realm.objects(Item), {name: itemSubscriptionName});
});
} else if (showImportantOnly) {
realm.subscriptions.update(mutableSubs => {
mutableSubs.removeByName(itemSubscriptionName);
mutableSubs.add(
realm.objects(Item).filtered(`owner_id == "${user?.id}" && priority <= 1`),
{name: ownItemsSubscriptionName},
);
});
} else {
realm.subscriptions.update(mutableSubs => {
mutableSubs.removeByName(itemSubscriptionName);
mutableSubs.add(
realm.objects(Item).filtered(`owner_id == "${user?.id}"`),
{name: ownItemsSubscriptionName},
);
});
}
}, [realm, user, showAllItems, showImportantOnly]);

Important

Don't forget to add showImportantOnly to the list of dependencies in the second argument of useEffect.

3

Your app is now set up to modify its sync subscription based on the current mode.

Rebuild and run the app to make sure everything works. You should be able to create, complete, and delete to-do items as well as toggle between viewing all items and only important items.

Tip

Changing Subscriptions with Developer Mode Enabled

In this tutorial, when you change the subscription and query on the priority field for the first time, the field is automatically added to the Device Sync Collection Queryable Fields. This occurs because the template app has Development Mode enabled by default. If Development Mode was not enabled, you would have to manually add the field as a queryable field to use it in a client-side Sync query.

For more information, refer to Queryable Fields.

Note

Share Feedback

How did it go? Use the Rate this page widget at the bottom right of the page to rate its effectiveness. Or file an issue on the GitHub repository if you had any issues.

Next

What are the Atlas Application Services?