Ditto for React Native runs on both Bare CLI and Expo environments and currently supports iOS and Android, with potential future platform support.

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)

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

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 and Android.

iOS

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

1

Install pods and make sure you use a compatible minimum iOS version.

From the ios/Podfile file in your project, set the minimum deployment target for iOS:

platform :ios, 14

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, 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

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.

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 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 Manifest

Android 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.xml

To 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

If using React Native’s Legacy Architecture, you must add the following code snippet to the module-level build.gradle located in the android/app directory:

android {
    packagingOptions {
        pickFirst 'lib/x86/libdittoffi.so'
        pickFirst 'lib/x86_64/libdittoffi.so'
        pickFirst 'lib/armeabi-v7a/libdittoffi.so'
        pickFirst 'lib/arm64-v8a/libdittoffi.so'
        
        pickFirst 'lib/x86/libjsi.so'
        pickFirst 'lib/x86_64/libjsi.so'
        pickFirst 'lib/armeabi-v7a/libjsi.so'
        pickFirst 'lib/arm64-v8a/libjsi.so'

        pickFirst 'lib/x86/libreact_nativemodule_core.so'
        pickFirst 'lib/x86_64/libreact_nativemodule_core.so'
        pickFirst 'lib/armeabi-v7a/libreact_nativemodule_core.so'
        pickFirst 'lib/arm64-v8a/libreact_nativemodule_core.so'

        pickFirst 'lib/x86/libturbomodulejsijni.so'
        pickFirst 'lib/x86_64/libturbomodulejsijni.so'
        pickFirst 'lib/armeabi-v7a/libturbomodulejsijni.so'
        pickFirst 'lib/arm64-v8a/libturbomodulejsijni.so'

        pickFirst 'lib/x86/libreactnative.so'
        pickFirst 'lib/x86_64/libreactnative.so'
        pickFirst 'lib/armeabi-v7a/libreactnative.so'
        pickFirst 'lib/arm64-v8a/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 Authentication

Create an identity object, define your authentication type, and then provide access credentials. Each instance of Ditto running in your app must prove its identity to other peers to sync.

There are two types of identities you can use to authenticate:

  • onlinePlayground — Explore platform features and functionality without setting up your authentication mechanism.
  • onlineWithAuthentication — If developing a production-level app, integrate your own identity provider, such as Auth0, identify users, give users read or write control, and integrate with your existing authentication systems.

For more information, see Cloud Authentication .

onlinePlayground

Use an online playground identity for development. You can find authentication details for your app on the Ditto Portal. Use these to set up your identity:

JS
const ditto = new Ditto({
    type: "onlinePlayground",
    appID: "REPLACE_ME_WITH_YOUR_APP_ID",
    token: "REPLACE_ME_WITH_YOUR_PLAYGROUND_TOKEN",
    customAuthURL: "REPLACE_ME_WITH_YOUR_AUTH_URL",
    enableDittoCloudSync: false, // This is required to be set to false to use the correct URLs
})

const config = new TransportConfig()
config.connect.websocketURLs.push('wss://REPLACE_ME_WITH_YOUR_WEBSOCKET_URL')

ditto.setTransportConfig(config)
ditto.startSync()

onlineWithAuthentication

Use the onlineWithAuthentication identity when developing a production app for deployment.

const identity = {
  type: 'onlineWithAuthentication',
  appID: 'REPLACE_ME_WITH_YOUR_APP_ID',
  authHandler: {
    authenticationRequired(authenticator) {
      if (authenticator.loginSupported) {
        authenticator.loginWithToken(
          'will-accept-any-password',
          'auth-webhook'
        );
        console.log('Successfully logged in');
      }
    },
    authenticationExpiringSoon: function (authenticator) {
      authenticator.loginWithToken('will-accept-any-password', 'auth-webhook');
      console.log('Successfully relogged');
    },
  },
};

Creating a New Instance of Ditto

Within a try/catch statement, instantiate a new Ditto object with the identity you’ve specified:

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

try {
  const ditto = new Ditto(identity);
} catch (error) {
  if (error instanceof DittoError) {
    // handle errors starting Ditto
  }
  throw error
}

Setting Transport Configurations

Using a TransportConfig instance, 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';
import {Platform} from 'react-native';


// ... Ditto init logic

const transportsConfig = new TransportConfig();
transportsConfig.peerToPeer.bluetoothLE.isEnabled = true;
transportsConfig.peerToPeer.lan.isEnabled = true;
transportsConfig.peerToPeer.lan.isMdnsEnabled = true;

// Apple Wireless Direct Link is only available on Apple devices
if (Platform.OS === 'ios') {
  transportsConfig.peerToPeer.awdl.isEnabled = 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.mutatedDocumentIDs().map((docId) => docId.value)
);

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.startSync();

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';

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.

Was this page helpful?