React Native and Expo guide

The React Native SDK is not a separate package. It is the @contentflow/sdk/react-native entry point of the same JavaScript SDK that serves Web and React, so one archive covers every JavaScript platform. The SDK is plain JavaScript with no native module of its own: the native code in your app comes from the packages listed under Dependencies, whose versions you choose.

This page covers which React Native and Expo versions work, how to set each one up, and what behaves differently from the web. The API itself (identity, consent, blocks, campaigns, events) is the same on every platform and is documented in the JavaScript SDK reference.

Compatibility at a glance

React Native Expo SDK Status What you need to do
0.79 and later 53 and later Supported Nothing beyond installing.
0.72 to 0.78 50 to 52 Supported with one Metro setting Turn on package exports in metro.config.js (see Configure Metro).
0.71 and earlier 49 and earlier Not supported Upgrade React Native.

Why the line falls there: the SDK publishes its entry points (/react-native, /expo, /react, …) through the exports map in its package.json. Metro, the React Native bundler, only reads that map by default from Metro 0.82, which ships with React Native 0.79 and Expo SDK 53. React Native 0.72 to 0.78 can read it when you opt in. Earlier Metro releases cannot read it at all, and the import fails with Unable to resolve module @contentflow/sdk/react-native.

The rest of the platform requirements are the same for every supported version:

Requirement Supported
React 17 or later (whatever your React Native version ships with)
JavaScript engine Hermes and JSC
React Native architecture New Architecture and the legacy architecture (the SDK has no native module, so it does not depend on either)
Expo workflow Expo Go, development builds and bare apps. Push notifications need a development build: Expo Go does not support push for your own app.
iOS and Android minimums Set by your React Native version and the native packages you add, not by the SDK

The React Native sample in the release (examples/react-native-app) is built and checked with Expo SDK 57, React Native 0.86 and React 19.2 for each release. The Metro behaviour above was checked by bundling an app on React Native 0.72.17 with and without the setting.

Dependencies

The SDK lists these as optional peer dependencies. Nothing is bundled into it, so you install the version that fits your React Native release and never get a second copy of React or React Native.

Package Needed for Versions
@react-native-async-storage/async-storage Required: the default storage for the device id, consent, segments and cached content 1.x, 2.x or 3.x. The SDK only calls getItem, setItem and removeItem, which all three keep. Use the release your React Native version supports (on Expo, npx expo install picks it).
react-native-sse Optional: live updates over a stream. Without it the SDK polls. Any. It is plain JavaScript.
@react-native-community/geolocation Optional: device location (requestLocation, startLocationTracking). React Native core no longer ships a location API. Any module with the navigator.geolocation shape works.
expo-notifications Optional: the Expo push helper, @contentflow/sdk/expo Any. You pass your own copy of the module in, so the SDK never imports it.

Install

  1. Download the SDK archive and its checksum file from Releases and downloads, and verify the archive as described in the JavaScript SDK install steps.
  2. Install the archive and AsyncStorage, plus any optional packages you need.

Bare React Native:

Terminal
npm install ./vendor/contentflow-sdk-<version>.tgz
npm install @react-native-async-storage/async-storage
npm install react-native-sse                      # optional: live updates
npm install @react-native-community/geolocation   # optional: device location
cd ios && pod install && cd ..

Expo:

Terminal
npm install ./vendor/contentflow-sdk-<version>.tgz
npx expo install @react-native-async-storage/async-storage
npx expo install react-native-sse                 # optional: live updates
npx expo install expo-notifications               # optional: push
  1. Configure Metro if your version needs it (next section).
  2. Rebuild the native app (npx react-native run-ios / run-android, or npx expo run:ios / run:android for a development build). AsyncStorage adds native code, so a JavaScript reload alone is not enough the first time.

Configure Metro

React Native 0.79+ and Expo SDK 53+

Nothing to do. Package exports are on by default. If your metro.config.js sets resolver.unstable_enablePackageExports = false (some apps added this to work around another library), the SDK import will fail; remove that line or set it to true.

React Native 0.72 to 0.78 (bare)

JavaScript
// metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');

const config = {
  resolver: {
    unstable_enablePackageExports: true,
  },
};

module.exports = mergeConfig(getDefaultConfig(__dirname), config);

Expo SDK 50 to 52

JavaScript
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');

const config = getDefaultConfig(__dirname);
config.resolver.unstable_enablePackageExports = true;

module.exports = config;

After changing the file, restart Metro with a clean cache: npx react-native start --reset-cache, or npx expo start -c.

This setting applies to every package in the app, not only ContentFlow. It is the same behaviour React Native turned on for everyone in 0.79, so most libraries are ready for it, but run through your app once after enabling it. If another library breaks, the practical fix is to upgrade that library or React Native itself.

Set up the provider

Wrap your app once in CFProvider. It creates the client, starts it, and disposes it when the provider unmounts.

TSX
import EventSource from 'react-native-sse';                     // optional: live updates
import Geolocation from '@react-native-community/geolocation';  // optional: device location
import { CFProvider, CFBanner } from '@contentflow/sdk/react-native';

export default function App() {
  return (
    <CFProvider
      config={{ publicKey: '<workspace-id>_test', locale: 'ar' }}
      eventSource={EventSource}
      geolocation={Geolocation}
    >
      <Home />
    </CFProvider>
  );
}

function Home() {
  return <CFBanner blockKey="home_banner" onPress={(b) => openLink(String(b.get('cta_url') ?? ''))} />;
}

@contentflow/sdk/react-native exports the same hooks as the React entry (useCF, useCFBlock, useCFBlocks, useCFCampaigns, useCFString, useCFTranslations, useCFTheme, useLiveStatus) plus two ready-made components, CFBanner and CFCampaignBanner. Both are optional: pass render to draw a block with your own components, or use the hooks directly.

To configure the platform yourself (for example, a different storage backend), build the client and hand it to the provider:

TSX
import { CFClient, CFProvider, createRNPlatform } from '@contentflow/sdk/react-native';

const config = { publicKey: '<workspace-id>_test' };
const client = new CFClient(
  config,
  createRNPlatform({ EventSource, geolocation: Geolocation, storage: myKeychainStorage }),
);

<CFProvider config={config} client={client}>…</CFProvider>;

Push registration with Expo is covered in Expo push. Bare apps register a native APNs/FCM token with client.registerPushWithReceipt({ token, platform: 'ios', provider: 'apns' }) (or 'android' / 'fcm'), as in the SDK guide.

What is different on React Native

Upgrading the SDK

  1. Download and verify the new archive, then npm install ./vendor/contentflow-sdk-<new-version>.tgz.
  2. Read that version's Action needed notes in the changelog.
  3. Restart Metro with a clean cache and rebuild the app.

Upgrading React Native itself does not require a new SDK. If you move to React Native 0.79 or Expo SDK 53 or later, you can delete the unstable_enablePackageExports line from metro.config.js.

Troubleshooting

Symptom Cause Fix
Unable to resolve module @contentflow/sdk/react-native Metro is not reading package exports: React Native 0.72 to 0.78 without the setting, or the setting forced to false Follow Configure Metro, then restart Metro with --reset-cache. On 0.71 or earlier, upgrade React Native.
Unable to resolve module @react-native-async-storage/async-storage AsyncStorage is not installed Install it, then rebuild the native app.
NativeModule: AsyncStorage is null AsyncStorage is installed but the native app was not rebuilt Run pod install (iOS) and rebuild, or create a new development build on Expo.
Content arrives, but only every few seconds No eventSource was passed, so the SDK polls Install react-native-sse and pass it to CFProvider.
requestLocation() rejects immediately No geolocation module was passed Pass geolocation={Geolocation} to CFProvider.
Push registration returns unsupported_token An Expo relay token (ExponentPushToken[...]) was used Use registerExpoPush, which requests the native APNs/FCM token. ContentFlow does not deliver through Expo's relay.

Generated from the ContentFlow repository by sdk-release/scripts/render-docs.mjs. Downloads and checksums are on Releases and downloads. The SDKs are licensed under the ContentFlow SDK License.