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-sdkPeer 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-2Then for iOS:
cd ios && pod installPlatform 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 scope — not 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:
| Marker | Means |
|---|---|
[SwanSDK] Starting SDK initialization... | getInstance(...) called for the first time |
[SwanSDK] SDK initialization completed successfully | Init 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 queued | Click 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
Identify users
Sign-in flow, anonymous-to-identified merge, profile enrichment, logout.
Track events
Screen tracking, typed e-commerce helpers, custom events, super-properties.
Push notifications
FCM wire-up details, channels, cold-start deep linking.
iOS — Rich pushes and extensions
Add a Notification Service Extension to render image and carousel pushes. Required for any non-text push on iOS.