Public Preview - This version is in public preview and subject to changes. For production use, please use SDK v4.
Ditto for React Native runs on both Bare CLI and Expo environments. Platform support: iOS and Android (CLI + Expo), macOS (CLI only).
1

Set up your React Native project (either CLI or Expo) (Prerequisites).
2

Install the Ditto package into your project. (Installing the Ditto SDK)
3

Install the project dependencies. (Installing Dependencies)
4

If using Expo Dev Builds, configure Ditto with the Expo plugin. If using React Native CLI, configure iOS and Android permissions. (Expo Setup or Configuring Permissions)
5

If using React Native CLI (Android), handle runtime permissions. (Handling Permissions)
6

Set up authentication. (Setting Up Authentication)
7

Specify how Ditto should handle sync. (Creating a New Instance of Ditto)
8

Configure peer-to-peer transport options. (Setting Transport Configurations)
9

Add Sync Subscription logic. (Constructing Sync Subscription Login)
10

Start the sync process. (Starting the Sync Process)
11

(Optional) Monitor remote peers. (Setting Up Presence)
12

(Optional) Set logs to debug. (Setting the Logs to Debug Level)
13

(Optional) Declare a foreground service (Android Only). (Declaring a Foreground Service)

Prerequisites

First, set up your environment and initialize your React Native project. For instructions, see React Native’s official documentation on Get Started with React Native.
To verify that you’ve set up your environment correctly, install and run the React Native CLI doctor. For more information, see the official blog post:Meet Doctor, a new React Native command React Native

Installing the Ditto SDK

From a terminal, navigate to the folder containing your React Native project, and then, using your preferred package manager, run the Ditto package:
yarn add @dittolive/ditto

Installing Dependencies

From the root of your project, install project dependencies based on the package.json file:
yarn install

Expo Setup

iOS and Android only - Expo does not support macOS. For macOS development, use React Native CLI.
Expo Go is not compatible with this SDK because it does not support custom native modules. You will need to use Expo Dev Builds (version 50+).
The React Native SDK provides an Expo Config Plugin to integrate Ditto into your Expo Dev Builds project. To enable this plugin, modify either app.json or app.config.js, depending on your project’s configuration:
{
  "expo": {
    "plugins": ["@dittolive/ditto"]
  }
}

Additional Requirements for Older Expo Projects

For older projects that have not yet migrated to:
  • Android minSdkVersion 24
  • Kotlin version 1.9.0
  • iOS Deployment Target 14.0
You must configure expo-build-properties to ensure compatibility. Add the following to app.json or app.config.js:
{
  "expo": {
    "plugins": [
      "@dittolive/ditto",
      [
        "expo-build-properties",
        {
          "android": {
            "minSdkVersion": 24,
            "kotlinVersion": "1.9.0"
          },
          "ios": {
            "deploymentTarget": "14.0"
          }
        }
      ]
    ]
  }
}

Additional Parameters (iOS only)

The Ditto Expo plugin allows additional iOS prompts configurations, which are applied in the following order of priority:
  1. Explicitly defined parameters in app.json or app.config.js.
  2. If no parameters are set, it falls back to the iOS native project’s Info.plist.
  3. If no values are found, Ditto uses its default values.
Below you will find an example of how to specify your values for the parameters.
{
  "expo": {
    "plugins": [
      [
        "@dittolive/ditto",
        {
          "bluetoothUsageDescription": "My custom BLE usage prompt message.",
          "localNetworkUsageDescription": "My custom LAN usage prompt message."
        }
      ]
    ]
  }
}

Configuring Permissions (non-Expo only)

Once you’ve added dependencies, set up the prerequisites to enable Ditto Transports for iOS, Android, and macOS.

iOS and macOS

From Xcode, enable Bluetooth LE and local network services in your app:
1

Install pods and make sure you use a compatible minimum deployment target.For iOS projects, from the ios/Podfile file in your project:
platform :ios, 14
For macOS projects, from the macos/Podfile file in your project:
platform :macos, '11.0'
Tip: No version changes are needed for RN 0.76+ projects that already have min_ios_version_supported set to 14.
Be sure to reinstall Pods afterward:
pod install
2

From your project’s Info.plist file (iOS: ios/YourApp/Info.plist, macOS: macos/YourApp/Info.plist), add the following key-value pairs:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>NSBluetoothAlwaysUsageDescription</key>
  <string>Uses Bluetooth to connect and sync with nearby devices.</string>
  <key>NSLocalNetworkUsageDescription</key>
  <string>Uses WiFi to connect and sync with nearby devices.</string>
  <key>NSBonjourServices</key>
  <array>
  <string>_http-alt._tcp.</string>
  </array>
  <!-- Your other keys -->
</dict>
</plist>
Tip: To view Info.plist as source code, right-click the file from the left sidebar in Xcode > click Open As > and then select Source Code from the menu.
If desired, customize the default values for the permission prompts by replacing them with your preferred text.For example, if your end users prefer a language other than English, you can replace the default English strings with their language equivalents.
Once implemented, these string values display to your end users as dismissable prompts explaining why the app requires certain permissions.
3

iOS only: Ensure your app continues to sync while it runs in the background by enabling Bluetooth LE background modes. Once enabled, your app continuously syncs in the background, even while the device is locked.
Note: Background modes are not available on macOS. This step only applies to iOS apps.
For official instructions, see Configuring background execution modes from Apple.
  1. From the left sidebar, click to select your project.
  2. Click Signing & Capabilities.
  3. Click + Capability, and then from the modal that appears, search and select Background Modes.
  4. From TARGETS, select your iOS app from the list.
  5. From Background Modes, toggle the following:
    • Uses Bluetooth LE accessories
    • Acts as a Bluetooth LE accessory

Android

From Android Studio, set up transport configurations for the Android target:
1

Update Android’s minimum SDK version to 24 or higher:
  1. Open the project-level build.gradle located in the android root directory.
  2. Set the minSDKVersion to 24.
For newer projects, set the SDK version in android/gradle.properties.
2

Declaring Permissions in the Android ManifestAndroid requires certain permissions to be explicitly requested by the app to access features like Bluetooth Low Energy and Wi-Fi Aware. These permissions must be declared in the app’s manifest file and requested from the end user at runtime.The Ditto SDK’s AndroidManifest.xml includes all of the necessary permissions for enabling its mesh network capabilities. These permissions will automatically be merged with your app’s permissions, so you should be aware of them.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission
        android:name="android.permission.BLUETOOTH_ADVERTISE"
        tools:targetApi="s" />
    <uses-permission
        android:name="android.permission.BLUETOOTH_CONNECT"
        tools:targetApi="s" />
    <uses-permission
        android:name="android.permission.BLUETOOTH_SCAN"
        android:usesPermissionFlags="neverForLocation"
        tools:targetApi="s" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission
        android:name="android.permission.NEARBY_WIFI_DEVICES"
        android:usesPermissionFlags="neverForLocation"
        tools:targetApi="tiramisu" />

    <!-- <application...> -->
</manifest>
Some of these permissions have an android:maxSdkVersion attribute which means they are not used on devices running newer versions of Android. This is a best practice to respect users’ privacy when those permissions are not necessary.However, some apps may still need to use one or more of the above permissions across more versions of Android. This can be accomplished by overriding the permission configuration in your app’s AndroidManifest.xmlTo override any of these permission limitations in your app, do the following:
1

Open the AndroidManifest.xml located in the app/src/main directory of your project.
2

Within the same <manifest> tag, just before the <application> tag, add the relevant permissions you want to configure (location example):
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" tools:remove="android:maxSdkVersion" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" tools:remove="android:maxSdkVersion" />
Note the additional tools:remove attribute. This tells the manifest merger to selectively remove the android:maxSdkVersion behavior from the associated permissions, changing them to apply to all Android versions.
For more information, see the official Permissions on Android and Merge manifest files documentation.
3

Add the following code snippet to the module-level build.gradle located in the android/app directory:
android {
    packagingOptions {
        pickFirst 'lib/**/libdittoffi.so'
        pickFirst 'lib/**/libjsi.so'
        pickFirst 'lib/**/libreact_nativemodule_core.so'
        pickFirst 'lib/**/libturbomodulejsijni.so'
        pickFirst 'lib/**/libreactnative.so'
    }
    // Rest of the file
}
4

Start the app in Metro:
yarn start

Handling Permissions

Within your default React component (App), use this helper function to request permissions if developing for the Android target:

import {useEffect} from 'react';
import {PermissionsAndroid, Platform} from 'react-native';

export default function App() {

  async function requestPermissions() {
    if (Platform.OS !== 'android') {
      return;
    }

    const granted = await PermissionsAndroid.requestMultiple([
      PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
      PermissionsAndroid.PERMISSIONS.BLUETOOTH_ADVERTISE,
      PermissionsAndroid.PERMISSIONS.NEARBY_WIFI_DEVICES,
      PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
    ]);

    Object.entries(granted).forEach(([permission, result]) => {
      if (result === PermissionsAndroid.RESULTS.GRANTED) {
        console.log(`${permission} granted`);
      } else {
        console.log(`${permission} denied`);
      }
    });
  }

  useEffect(() => {
    requestPermissions();
  }, []);

}

Setting Up Configuration

Create a DittoConfig object to configure your Ditto instance. You can choose from several connection modes:

Online Playground (easiest for development)

const databaseId = 'REPLACE_ME_WITH_YOUR_DATABASE_ID';
const connectConfig = {
  mode: 'server',
  url: `https://${databaseId}.cloud.ditto.live`,
};
const config = new DittoConfig(databaseId, connectConfig);

Online with Authentication (production)

const connectConfig = {
  mode: 'server',
  url: 'https://auth.your-company.com',
};
const config = new DittoConfig('REPLACE_ME_WITH_YOUR_DATABASE_ID', connectConfig);

Local Development (P2P only)

const connectConfig = {
  mode: 'smallPeersOnly',
};
const config = new DittoConfig('REPLACE_ME_WITH_YOUR_DATABASE_ID', connectConfig);

P2P Production (with shared key)

const connectConfig = {
  mode: 'smallPeersOnly',
  privateKey: 'your-base64-encoded-private-key-here',
};
const config = new DittoConfig('REPLACE_ME_WITH_YOUR_DATABASE_ID', connectConfig);

Creating a New Instance of Ditto

import {Ditto, DittoConfig} from '@dittolive/ditto';

// Create your config (use examples from above)
const config = new DittoConfig(databaseId, connectConfig);

// Open Ditto instance
const ditto = await Ditto.open(config);

Setting Up Authentication

For server mode configurations, authenticate after opening Ditto:
import {Authenticator} from '@dittolive/ditto';

// Set up token refresh
await ditto.auth.setExpirationHandler(async (ditto, timeUntilExpiration) => {
  console.log('Token expiring soon:', timeUntilExpiration);
  
  if (ditto.auth.loginSupported) {
    const result = await ditto.auth.login('your-playground-token', Authenticator.DEVELOPMENT_PROVIDER);
    if (result.error) {
      console.error('Re-authentication failed:', result.error);
    }
  }
});

// Initial login
if (ditto.auth.loginSupported) {
  const result = await ditto.auth.login('your-playground-token', Authenticator.DEVELOPMENT_PROVIDER);
  if (result.error) {
    console.error('Login failed:', result.error);
  } else {
    console.log('Successfully logged in');
  }
}

Setting Transport Configurations

Using updateTransportConfig, do the following to set up transport configurations in your app:
1

Configure peer-to-peer transport settings so that all desired transport types available on the end-user device are available to Ditto.
2

Specify how you want Ditto to handle which transports to sync data across the mesh.
import {TransportConfig} from '@dittolive/ditto';

// Recommended: Use setAvailablePeerToPeerEnabled to automatically
// enable all transports that are available on the current platform
const transportsConfig = new TransportConfig();
transportsConfig.setAvailablePeerToPeerEnabled(true);
ditto.setTransportConfig(transportsConfig);

Constructing Sync Subscription Logic

Create the logic that performs sync and data operations in your app, including registering subscriptions, executing store operations, and observing changes in the given collection. For example, the following snippet defines a document object (document), creates a subscription for the Cars collection, executes database operations and logs the results, registers an observer for watching changes in the Cars collection:

// ... Ditto init logic

ditto.sync.registerSubscription(`SELECT * FROM cars`);

const document = {
  id: 987654,
  _id: 123131,
  model: "CX-5",
  make: "Mazda",
  color: "blue",
};

const queryResult = await ditto.store.execute(
  "INSERT INTO cars DOCUMENTS (:document) ON ID CONFLICT DO UPDATE",
  { document }
  );
console.log(queryResult.items.map((item) => item.value));
console.log(
  "mutated",
  queryResult.mutatedDocumentIDsV2()
);

ditto.store.registerObserver(`SELECT * FROM cars`, (response) => {
  const parsedDocuments = response.items.map((doc) => {
    return doc.value;
  });

  // save state or other use cases
});

Starting the Sync Process

To start syncing with other peers in the mesh:
ditto.sync.start();

Setting Up Presence

Monitor remote peers in the logs or using Ditto’s Presence Viewer app. For more information, see the blog post “An explanation of the Ditto Presence Viewer. To set up presence observations:
// This closure is called every time the mesh of
// connected Ditto peers changes:
ditto.presence.observe((graph) => {
  console.log('Peers: ', graph.remotePeers);
});

Setting Logs to Debug Level

Capture database debug logs by setting the Ditto log level to Debug mode. That way, any potential issues are tracked. To set the minimum log level to Debug:
import {Logger} from '@dittolive/ditto';

...

Logger.minimumLogLevel = 'Debug';

Declaring a Foreground Service (Android Only)

A foreground service allows your app to continue syncing data with Ditto while running in the background. A foreground service is not required to use Ditto, but without it your app will stop syncing whenever it is in the background. To declare the foreground service, add the following to your AndroidManifest.xml:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" tools:targetApi="upside_down_cake" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" tools:targetApi="tiramisu" />

<application ...>
    <service
        android:name="live.ditto.transports.foregroundservice.DefaultDittoForegroundService"
        android:exported="false"
        android:foregroundServiceType="connectedDevice"
        android:stopWithTask="true">
        <meta-data android:name="notificationChannelName" android:value="Background Data Sync" />
        <meta-data android:name="notificationChannelId" android:value="BackgroundDataSyncId" />
        <meta-data android:name="smallIcon" android:resource="@drawable/small_app_icon" />
        <meta-data android:name="notificationTitle" android:value="Syncing Data" />
        <meta-data android:name="notificationText" android:value="Syncing data across connected devices" />
        <meta-data android:name="notificationId" android:value="1001" />
    </service>
</application>
Make sure you customize the service metadata—it will be displayed to the user in the foreground service notification. Note that your small_app_icon drawable must be monochrome white. The foreground service will be automatically started and stopped on startSync() and stopSync(), respectively.

Known issues

  1. React Native’s Fast Refresh will throw an error with the current Ditto architecture, insisting you provide a different persistence directory name. Currently, no workaround is available; however, developers can disable Fast Refresh to avoid this issue. Our team is actively working on a fix, and we appreciate your patience as we address this challenge.