Swan SDK
Getting Started

React Native

Install, initialize, and verify your first event on React Native.

React Native 0.70+ · iOS 13.0+ · Android API 21+ · Distribution: npm — @loyalytics/swan-react-native-sdk

React Native architecture. The SDK runs on both the legacy and the new React Native architecture (TurboModule + Fabric). No SDK configuration is required — when your host app enables the new architecture (newArchEnabled=true in Android gradle.properties and in ios/Podfile.properties.json), the SDK matches it automatically. Apps that haven't opted in continue to run on the legacy bridge with no behavior change.

Install

yarn add @loyalytics/swan-react-native-sdk
# or
npm install @loyalytics/swan-react-native-sdk

Peer dependencies

The SDK depends on a small set of standard React Native libraries for push, network, and local storage. Install them in your host app:

yarn add @react-native-firebase/app \
         @react-native-firebase/messaging \
         @notifee/react-native \
         @react-native-async-storage/async-storage \
         @react-native-community/netinfo \
         react-native-device-info \
         react-native-sqlite-2

Then for iOS:

cd ios && pod install

Platform setup

Add the runtime permissions the SDK needs to android/app/src/main/AndroidManifest.xml:

<manifest ...>
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- Android 13+ -->

  <!-- Only required if you enable location tracking in SwanSDKConfig.location -->
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
</manifest>

Drop the google-services.json from your Firebase console into android/app/ so Firebase Messaging can register a token.

Place GoogleService-Info.plist from your Firebase console into the root of your Xcode project and add it to every target.

In Xcode, enable Push Notifications and Background Modes → Remote notifications on your app target.

If your campaigns include image or carousel pushes, add a Notification Service Extension (and, for carousels, a Notification Content Extension) to your project — see iOS — Rich pushes and extensions. Without the Service Extension, image and carousel notifications display as title + body only.

If you plan to use location tracking, add the usage description keys to ios/YourApp/Info.plist:

<key>NSLocationWhenInUseUsageDescription</key>
<string>We use your location to provide personalized local offers.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>We use your location to provide personalized local offers.</string>

Initialize

The SDK is a singleton. Initialize it at the top of your app — typically in App.tsx (or your top-level navigation container):

import SwanSDK from '@loyalytics/swan-react-native-sdk';

const sdk = SwanSDK.getInstance('your-app-id', {
  logging: __DEV__,         // verbose SDK logs in dev builds
  isProduction: true,       // production backend; set false for staging
  pushNotifications: {
    enabled: true,          // opt anonymous users into push, too
    autoRequestPermission: false,
  },
  location: {
    enabled: false,         // opt in only if you call updateLocation()
  },
});

Subsequent calls to SwanSDK.getInstance(...) return the same instance. If you need the already-created instance from a different module, use SwanSDK.getCurrentInstance() (returns null before the first getInstance call).

isProduction defaults to false (staging). Production builds must set isProduction: true.

Wire push notification handlers

Register the message handlers in your app's entry point (index.js), at module scopenot inside a component. Background and killed-state deliveries fire before any React tree is mounted, so handlers attached inside useEffect will not run for them.

// index.js
import { AppRegistry } from 'react-native';
import App from './App';
import { name as appName } from './app.json';

import messaging from '@react-native-firebase/messaging';
import notifee from '@notifee/react-native';
import {
  createForegroundMessageHandler,
  createBackgroundMessageHandler,
  createNotificationOpenedHandler,
  createNotifeeForegroundHandler,
  createNotifeeBackgroundHandler,
} from '@loyalytics/swan-react-native-sdk';

// Firebase Messaging — data-message delivery
messaging().onMessage(createForegroundMessageHandler());
messaging().setBackgroundMessageHandler(createBackgroundMessageHandler());

// Notifee — click tracking on the displayed notification
notifee.onForegroundEvent(createNotifeeForegroundHandler());
notifee.onBackgroundEvent(createNotifeeBackgroundHandler());

// Firebase — iOS carousel taps come through this path, not Notifee
messaging().onNotificationOpenedApp(createNotificationOpenedHandler());

AppRegistry.registerComponent(appName, () => App);

iOS carousel notifications are displayed natively by iOS (via the Notification Content Extension), so Notifee's click handlers do not fire on a carousel tap. The messaging().onNotificationOpenedApp(...) handler is what receives the per-item route — register it even if your host app does not otherwise use react-native-firebase's notification UI.

Request notification permission

On Android 13+ (API 33) and iOS, ask for permission after a meaningful user action (sign-in, add-to-cart, completed onboarding) — not at cold launch. Permission denials are sticky on both platforms.

const granted = await sdk.requestNotificationPermission();
if (granted) {
  // happy path — token registration runs in the background
} else {
  // soft-prompt the user later
}

If you prefer the SDK to prompt automatically on first launch, set pushNotifications.autoRequestPermission: true in SwanSDKConfig.

Verify the integration

Six [SwanSDK] * log markers emit at key lifecycle points:

MarkerMeans
[SwanSDK] Starting SDK initialization...getInstance(...) called for the first time
[SwanSDK] SDK initialization completed successfullyInit resolved
[SwanSDK] Device registered successfully: <uuid>Device-register HTTP call succeeded; CDID granted
[SwanSDK] Foreground notification received: <messageId>FCM push arrived while app was in foreground
[SwanSDK] Notification displayed successfully with ID: <messageId>Notification posted to OS
[SwanSDK] Notification ACK queuedClick ACK queued for backend

Visible in the Metro bundler output during dev, adb logcat (Android), or the iOS Console / Xcode debug pane.

You can also subscribe to the lifecycle events directly:

sdk.addListener('initialized', () => {
  console.log('Swan SDK is ready');
});

sdk.addListener('deviceRegistered', (credentials) => {
  console.log('device registered:', credentials.deviceId);
});

sdk.addListener('networkStateChanged', ({ online }) => {
  console.log('connectivity changed:', online ? 'online' : 'offline');
});

addListener returns a subscription with a .remove() method — hold on to it and call .remove() if you need to detach the handler later.

The networkStateChanged event fires on every connectivity transition with a { online: boolean } payload — use it to pause or resume network-dependent UI when the device goes offline and comes back.

Track your first event

const sdk = SwanSDK.getCurrentInstance();
sdk?.productViewed({
  productId: 'SKU-100',
  productName: 'Test product',
  price: 9.99,
  currency: 'USD',
});

Identify the signed-in user

If your host app already has a stored user session at launch, assert it to the SDK with identify(...) — idempotent, safe to call on every cold start, and does not emit a USER_LOGIN event:

const stored = await myApp.getStoredUserMobile();
if (stored) {
  await sdk.identify(stored);
}

Use login(...) instead from the credential-submit handler (it emits USER_LOGIN for analytics). See the Identify users guide for the full flow.

Next steps

On this page