React Native Integration
This guide shows how to add the AlgorithmX SDK to a React Native app that is already in production, with its own login, analytics, push notifications, and navigation. You do not rebuild any of these. You add a few calls in the places where your app already handles startup, sign-in, business events, notifications, and navigation. The SDK does the rest: it shows campaign notifications and in-app campaigns, tracks interactions, and applies frequency rules.
The package react-native-engage-sdk wraps the native Android and iOS SDKs. The work splits in two:
- Native code (a few lines): start the SDK in
MainApplicationandAppDelegate, and pass push tokens and notifications to it. This has to be native because a push can start your app before JavaScript runs. - JavaScript: identify customers, send events, and handle campaign navigation.
Names such as appRouter in the examples stand for your own code; they are not SDK APIs.
How the SDK fits into your app
| Where in your existing app | What you add | Section |
|---|---|---|
| package.json, Podfile, settings.gradle | The package and the native SDKs it wraps. | 2 |
| MainApplication and AppDelegate | One line to start the SDK. | 3 |
| JavaScript startup | Register the SDK listeners and bind JavaScript to the SDK. | 3 |
| Login, restored session, logout | Tell the SDK who the customer is. | 4 |
| Business events | Forward the agreed events. | 5 |
| Your push setup (Firebase on Android, APNs on iOS) | Pass tokens and AlgorithmX notifications to the SDK. | 6 |
| Your navigation | Open your screens from campaign links, custom actions, and buttons. | 7 |
In-app campaigns need no screen or view code from you: once these connections are in place, the SDK shows campaigns by itself (section 8).
1. Before you start
What AlgorithmX gives you
- The
react-native-engage-sdkpackage with its version, plus the native SDKs it wraps: the Android SDK module and the iOSEngageSDKpod. - The API base URL for each environment, such as staging and production. All URLs use HTTPS.
What your team gives AlgorithmX
AlgorithmX sends notifications through Firebase on Android and through Apple's push service (APNs) on iOS, so it needs the same details as a native app:
| Platform | What to send | Details |
|---|---|---|
| Android | Application ID, your Firebase project, and access to send messages through it. | Android guide, section 1 |
| iOS | Bundle ID, Team ID, APNs authentication key and Key ID, and which build type each workspace serves. | iOS guide, section 1, including the Apple Developer account checklist |
Also agree with the AlgorithmX team on the customer ID you will use (section 4), the events you will send (section 5), and the deep links and custom actions campaigns may use (section 7).
Expo: the package contains native code, so it does not run in Expo Go. Expo projects need a development build, and the native changes in sections 3 and 6 must survive prebuild, for example through a config plugin.
2. Add the package
Install the package from the location AlgorithmX gives you, for example a package file, then connect the native SDKs it wraps. The file name and paths below are examples.
npm install ./react-native-engage-sdk-1.0.0.tgzAndroid: include the Android SDK module in android/settings.gradle, and add it to your app module so your native code can call it:
// android/settings.gradle
include ':android-sdk'
project(':android-sdk').projectDir = new File(rootDir, '../algorithmx/android-sdk')// android/app/build.gradle, existing dependencies block
implementation project(':android-sdk')iOS: add the native SDK pod to your app target in ios/Podfile, then run pod install. Autolinking adds the package's own pod.
target 'YourApp' do
pod 'EngageSDK', :path => '../algorithmx/ios-sdk'
# Keep your existing pods and React Native setup.
endThe SDK needs Android minSdk 24 and iOS 15.1 or later. It works with the New Architecture and was verified with React Native 0.87.
If you install the package from a folder instead of a package file, npm links the folder instead of copying it. In that case, add the folder to watchFolders in metro.config.js, set resolver.nodeModulesPaths to your app's node_modules, and set preserveSymlinks: true in tsconfig.json.
3. Initialize the SDK
In native code
Start the SDK in your existing native entry points, before React Native loads. A push can start your app in the background, and this is the code that runs then.
Android, in MainApplication.onCreate:
override fun onCreate() {
super.onCreate()
AlgorithmXReactNative.initialize(this, "https://api.example.com")
AlgorithmX.setSmallIcon(R.drawable.ic_stat_notification) // Optional: your notification icon.
// Keep your existing startup code, including loadReactNative(this).
}iOS, in application(_:didFinishLaunchingWithOptions:) of your AppDelegate:
import EngageRnSdk
import EngageSDK
// Inside application(_:didFinishLaunchingWithOptions:), before React Native starts:
AlgorithmXReactNative.initialize(apiBaseUrl: "https://api.example.com", appGroup: "group.com.example.shop")
UNUserNotificationCenter.current().delegate = self // Keep your existing delegate assignment.
application.registerForRemoteNotifications() // Every launch, independent of permission.The App Group is optional; it is only needed for iOS delivery reporting (section 6). Without it, call AlgorithmXReactNative.initialize(apiBaseUrl:). The examples use Swift, which React Native 0.77 and later generate; with an older Objective-C AppDelegate, make the same calls from Objective-C.
In JavaScript
At your app's entry, before the first screen renders, register your listeners (section 7) and then bind JavaScript to the SDK with the same URL:
import EngageSdk from 'react-native-engage-sdk';
// Register the listeners from section 7 here first.
async function start() {
await EngageSdk.init('https://api.example.com');
}
start();initconnects JavaScript to the SDK that native code already started. It rejects withE_INITif the URL differs from the native one.- Do not register native handlers yourself. The package already connects the SDK's native callbacks to JavaScript; setting
deepLinkHandlerorsetNotificationClickListenernatively would disconnect them. - To see each SDK request while testing, call
AlgorithmX.setLoggingEnabled(true)(Android) orAlgorithmX.shared.setLoggingEnabled(true)(iOS) in the native startup code.
4. Identify your customers
Until you identify a customer, the SDK uses an anonymous device ID. Call identify with the same customer ID your backend uses, in two places: after a successful login, and when your app restores a saved session at startup. The second one matters for customers who are already signed in when they update to the first release with AlgorithmX.
// In the existing restored-session or successful-login code.
EngageSdk.identify('customer_123', { first_name: 'Amina', language: 'ar', loyalty_tier: 'gold' });The SDK remembers the customer across app restarts and sends their previous anonymous ID, so AlgorithmX can link earlier activity to the profile. At logout, and when one account replaces another, clear waiting campaigns so the next person does not see them:
// Before completing logout:
await EngageSdk.clearWebViewQueue();
EngageSdk.resetIdentity();
// Before replacing one signed-in account with another:
await EngageSdk.clearWebViewQueue();
EngageSdk.identify('customer_456', { language: 'en' });resetIdentity() returns to the anonymous device ID. AlgorithmX identity follows your login; it does not replace your authentication.
5. Track business events
Forward the agreed events from the places where your app already knows an action succeeded, for example your order-confirmation handler, next to your existing analytics calls.
EngageSdk.trackEvent('product_view', { product_id: 'SKU-123' });
EngageSdk.trackEvent('add_to_cart', { product_id: 'SKU-123', quantity: 2 });
EngageSdk.trackEvent('purchase', {
order_id: 'ORDER-1001',
total: 149.9,
currency: 'SAR',
items: [{ product_id: 'SKU-123', quantity: 2 }],
});- Use plain JSON values: strings, numbers, booleans, arrays, and objects. Send dates as timestamps or ISO 8601 strings.
- Use simple event names such as
add_to_cart; the name becomes part of the request URL. - Events are not retried. An event sent while the device is offline is lost, so keep your own backend as the source of truth for orders and payments.
6. Connect your existing push notifications
Your app keeps its current push setup. On each platform you pass the device token to AlgorithmX and pass AlgorithmX notifications to the SDK.
Android
If your app has its own FirebaseMessagingService, add the AlgorithmX branch and token registration exactly as in section 6 of the Android guide. The branch goes before your own notification code:
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
if (AlgorithmX.isAlgorithmXPush(message.data)) {
AlgorithmX.handleFcmMessage(
applicationContext, message.data,
message.notification?.title, message.notification?.body
)
return
}
// Keep your current message handling here.
}
override fun onNewToken(token: String) {
super.onNewToken(token)
AlgorithmX.registerDeviceToken(token)
}Also register the current token on every launch in MainApplication.onCreate:
FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
AlgorithmX.registerDeviceToken(token)
}If your app handles Firebase messages in JavaScript with @react-native-firebase/messaging, forward AlgorithmX messages from your existing handlers instead. Your background handler stays in index.js, outside any component, as Firebase requires:
import { Platform } from 'react-native';
import messaging, { FirebaseMessagingTypes } from '@react-native-firebase/messaging';
import EngageSdk from 'react-native-engage-sdk';
async function forwardToAlgorithmX(message: FirebaseMessagingTypes.RemoteMessage) {
const data = (message.data ?? {}) as Record<string, string>;
if (data.engage_action === 'algo_show_notification' || data.engage_action === 'algo_trigger_webview') {
EngageSdk.handleFcmMessage(data, message.notification?.title, message.notification?.body);
}
}
messaging().setBackgroundMessageHandler(async (message) => {
await forwardToAlgorithmX(message);
// Keep your existing background handling here.
});
messaging().onMessage(async (message) => {
await forwardToAlgorithmX(message);
// Keep your existing foreground handling here.
});
// Android only: on iOS, AlgorithmX needs the APNs token, registered natively below.
if (Platform.OS === 'android') {
messaging().getToken().then((token) => EngageSdk.registerDeviceToken(token));
messaging().onTokenRefresh((token) => EngageSdk.registerDeviceToken(token));
}In both cases, keep your existing notification permission prompt; the SDK never asks for permission.
iOS
iOS notifications go through your AppDelegate, so this part is native and identical to section 6 of the iOS guide. Add the AlgorithmX lines to your existing token callback and notification delegate methods:
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let apnsToken = deviceToken.map { String(format: "%02x", $0) }.joined()
AlgorithmX.shared.registerDeviceToken(apnsToken)
}
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
if AlgorithmX.shared.isAlgorithmXPush(userInfo: userInfo) {
AlgorithmX.shared.handleNotification(userInfo: userInfo) { completionHandler(.newData) }
return
}
// Keep your current handling here.
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
let userInfo = notification.request.content.userInfo
if AlgorithmX.shared.isAlgorithmXPush(userInfo: userInfo) {
AlgorithmX.shared.handleNotification(userInfo: userInfo) { completionHandler([.banner, .list, .sound]) }
return
}
// Keep your current foreground presentation here.
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
let userInfo = response.notification.request.content.userInfo
if AlgorithmX.shared.isAlgorithmXPush(userInfo: userInfo) {
AlgorithmX.shared.handleNotificationResponse(
actionIdentifier: response.actionIdentifier,
userInfo: userInfo,
completionHandler: completionHandler
)
return
}
// Keep your current tap handling here.
}- Send the APNs token, even if your app also uses Firebase on iOS.
- If a library such as Firebase or Notifee owns your notification delegate, send a real AlgorithmX notification during testing to check that these lines still run.
- Enable Background Modes, Remote notifications in Xcode; in-app campaigns need it.
- Images and buttons (optional): add the call from section 10 of the iOS guide to your notification service extension. React Native apps use CocoaPods, so compile the SDK file
EngageNotificationService.swiftinto the extension instead of adding the pod there.
7. Handle navigation in JavaScript
When a customer taps an AlgorithmX notification, the SDK sends an event to JavaScript. Your listeners open the screens:
| Event | When | What your listener does |
|---|---|---|
onDeepLink | The campaign action is "open screen". | Open the link with your router. The SDK does not navigate. |
onCustomAction | The campaign action is a custom action. | Run the named action with its parameters. |
onActionButton | The customer taps a notification button. | Handle the button. Every button comes to JavaScript, and the notification's main action does not run afterwards. |
onNotificationClick | Every tap on the notification itself, for information. | Nothing required. The SDK already runs the campaign's action, so do not navigate here too. |
"Open web page" and "open in-app campaign" actions are handled by the SDK and need no listener.
import EngageSdk from 'react-native-engage-sdk';
import { appRouter } from './navigation/appRouter'; // Your existing navigation code.
EngageSdk.addListener('onDeepLink', ({ url }) => appRouter.openUrl(url));
EngageSdk.addListener('onCustomAction', ({ action, data }) => {
const params = (data.parsedActionData ?? {}) as Record<string, unknown>;
if (action === 'view_product' && typeof params.product_id === 'string') {
appRouter.openProduct(params.product_id);
} else if (action === 'view_cart') {
appRouter.openCart();
}
});
EngageSdk.addListener('onActionButton', ({ actionText }) => {
if (actionText === 'view_cart') appRouter.openCart();
});- Register all listeners together, at app startup, before the first render, for example in
index.jsor a module it imports. The SDK holds events that arrive before JavaScript is ready, such as the tap that launched the app, and delivers them as soon as the first listener is added. Listeners added later, inside a screen, can miss them. - Your router must wait for your navigation to be ready. On a tap that launches the app, events arrive before your navigation container is mounted. Keep the destination and open it once navigation is ready (with React Navigation, when
navigationRef.isReady()is true or inonReady). - Reuse your existing links. Give the campaign team the list of links your app supports, with their required parameters, for example
myapp://open/product?id=SKU-123. You do not need new URL schemes or screens. - Agree on action names and parameter types with the campaign team, and only offer campaign actions that your released app version implements. Match buttons on their action text, not their translated title.
- A campaign parameter is only a navigation hint: keep your normal login and permission checks before sensitive actions.
8. In-app campaigns
In-app campaigns need no extra code. With sections 3 to 6 in place:
- AlgorithmX sends a background message to the device, and your native push code passes it to the SDK.
- The SDK saves the campaign.
- The next time the customer opens or returns to the app, the SDK shows the campaign over the current screen, at most one per visit. It closes with the campaign's close button, or with Back on Android.
Points to check in your app:
- Logout and account switch: clear waiting campaigns as shown in section 4.
- Your own pop-ups at launch: a campaign can appear at the moment the app opens, on top of whatever is on screen. Check the combination with any modal your app shows at startup.
- Close button: iOS has no Back button, so every campaign template must include a close button. Remind your campaign team.
- Notification permission is not needed for in-app campaigns.
- Delivery is best effort when the customer force-stops the app (Android) or force-closes it from the app switcher (iOS): messages arrive only after the app is opened again.
To follow campaign activity while testing, listen to onCampaignInteraction; it reports each impression, click, and close.
9. Test and ship
Test on real Android and iOS devices with a test customer, using release builds as well as debug builds. On iOS, repeat the main checks with a TestFlight build, because store builds use a different push environment.
| Check | Expected result |
|---|---|
| Update from your current store version while signed in | The customer is identified without logging in again, and the token is registered. |
| Login, restart, logout, second account | Events use the right customer each time, and no campaign from the first account appears for the second. |
| Your existing notifications | Other notifications behave exactly as before. |
| AlgorithmX notification with the app open, in the background, and closed | A tap opens the right screen once, including when the tap launches the app. |
| Each deep link, custom action, and button your app supports | The right screen opens; unknown links and actions follow your app's normal fallback. |
| In-app campaign | It appears the next time the app becomes active, over the current screen, and closes with its close button. |
| Notification permission denied | In-app campaigns still arrive; visible notifications do not. |
| Business events | The agreed events appear for the customer in AlgorithmX. |
API reference
| Call | Use |
|---|---|
AlgorithmXReactNative.initialize(this, apiBaseUrl) (Android) | Start the SDK in MainApplication.onCreate. |
AlgorithmXReactNative.initialize(apiBaseUrl:appGroup:) (iOS) | Start the SDK in the AppDelegate. The App Group is optional. |
EngageSdk.init(apiBaseUrl) | Bind JavaScript to the SDK; use the same URL as native code. |
EngageSdk.identify(userId, attributes) | Identify the customer after login and when a session is restored. |
EngageSdk.resetIdentity() | Return to the anonymous device ID at logout. |
EngageSdk.trackEvent(name, properties) | Send a business event. |
EngageSdk.registerDeviceToken(token) | Pass a token from JavaScript, when your token handling lives there. |
EngageSdk.handleFcmMessage(data, title, body) | Android: forward an AlgorithmX message received in JavaScript. |
EngageSdk.addListener(event, handler) | Receive onDeepLink, onCustomAction, onActionButton, onNotificationClick, onCampaignInteraction. Returns a subscription with remove(). |
EngageSdk.clearWebViewQueue() | Remove waiting campaigns at logout or account switch. |
EngageSdk.getDeviceFingerprint(), EngageSdk.getWebViewQueueSize() | Inspect the current customer ID and waiting campaigns while testing. |
The native calls in section 6 (AlgorithmX.handleFcmMessage, AlgorithmX.shared.handleNotification, and so on) are the same as in the Android and iOS guides.
Troubleshooting
| Symptom | What to check |
|---|---|
| "Native module not found" at startup | Android: :android-sdk is included in settings.gradle. iOS: the EngageSDK pod is in the Podfile and pod install ran. Rebuild the app after installing. |
| init rejects with E_INIT | JavaScript and native code use different URLs. Use the same value in both. |
| No events in AlgorithmX | Native initialization runs at startup with the right HTTPS URL, and the device is online. |
| No AlgorithmX notifications arrive | See the troubleshooting section of the Android or iOS guide: token registration, sending access, and forwarding. |
| Tap on a notification does not reach JavaScript | Listeners are registered at startup, all together, before the first render; native code does not set its own SDK handlers. |
| Tap that launches the app opens nothing | Your router keeps the destination until navigation is ready. |
| Screen opens twice after a tap | You navigate in onNotificationClick as well as in onDeepLink or onCustomAction. Navigate only in the specific listener. |
| A button does nothing | Your onActionButton listener has no case for that action text; the main action does not run as a fallback. |
| In-app campaign never appears | Native push forwarding is in place, iOS Background Modes is on, the app was not force-closed, and you reopened the app after the campaign was sent. |
| A previous account's campaign appears | Call clearWebViewQueue before logout and account switch. |
For creating campaigns, continue with In-App Push Campaigns and In-App View Campaigns. Return to Integrations for the other platforms.
