React Native SDK
Full lifecycle analytics for iOS and Android — screens, deep links, crashes, experiments, and push notifications.
The React Native SDK extends the core SDK with mobile-specific features: app lifecycle, screen views via React Navigation, deep-link capture, native crash reporting, and push notification lifecycle helpers.
Installation
Install packages
npm install @flash-analytics/react-native@2.1.19 react-native-device-infocd ios && pod installreact-native-device-info is a required peer dependency for device metadata. Run pod install after adding it on iOS. @react-native-async-storage/async-storage. Without it, the SDK still batches in memory for the current app process.Create an analytics module
Initialize the SDK once and export both the analytics instance and the navigation ref.
import { createNavigationContainerRef } from '@react-navigation/native';
import { FlashAnalytics } from '@flash-analytics/react-native';
export const navigationRef = createNavigationContainerRef();
export const analytics = new FlashAnalytics({
appId: process.env.FLASH_APP_ID!, // must be a UUID
captureAppLifecycle: true,
captureDeepLinks: true,
captureScreenViews: true,
captureErrors: true,
captureNativeCrashes: true,
captureVariants: true,
onSessionUpdated(session) {
console.log(session.id);
},
deliveryStrategy: {
strategy: 'batching',
config: {
size: 10,
timeout: 5000,
fallbackToIndividual: true,
},
},
});
analytics.setNavigationRef(navigationRef, (routeName, params) => {
if (routeName === 'Product') return `/product/${(params as any)?.id}`;
return `/${routeName.toLowerCase()}`;
});Connect React Navigation
import { NavigationContainer } from '@react-navigation/native';
import { navigationRef } from './analytics';
export function App() {
return (
<NavigationContainer ref={navigationRef}>
{/* your navigators */}
</NavigationContainer>
);
}Identify users and track events
import { analytics } from './analytics';
// After login
await analytics.identify({
profileId: 'user_123',
email: 'user@example.com',
});
// Track product events
await analytics.track('product_added_to_cart', {
productId: 'sku_123',
price: 29.99,
});Auto-capture flags
captureAppLifecycleapp_opened, app_foregrounded, app_backgrounded
captureSessionOnInitsdk_initialized session warm-up before lifecycle events
captureScreenViewsscreen_view on each navigation change
captureDeepLinksdeep_link_opened for URL opens
captureInstallAttributionInstall / reopen attribution
captureErrorsGlobal JS errors and unhandled rejections
captureNativeCrashesnative_crash on next app launch
capturePushLifecyclePush notification lifecycle helpers
captureVariantsA/B experiment auto-assignment
Error tracking
try {
await checkout();
} catch (error) {
analytics.trackError(error, 'checkout_failed', {
orderId: cart.orderId,
});
}Experiments
// All cached assignments — no API call
const all = analytics.getAllExperiments();
// Cache first, API fallback
const assignment = await analytics.getExperimentById('checkout-cta');
console.log(assignment?.variantName);
// Manual refresh
const assignments = await analytics.autoAssignExperiments();Push notifications
The JS layer handles what it can. For full coverage you need native host code on both platforms.
| Event | JS only | Native required |
|---|---|---|
notification_opened | None — JS handler is enough | |
notification_delivered (iOS) | UNNotificationServiceExtension | |
notification_expired (iOS) | UNNotificationServiceExtension timeout | |
notification_dismissed (Android) | BroadcastReceiver + PendingIntent | |
notification_action_clicked (Android) | BroadcastReceiver + PendingIntent |
JS — enable push tracking and handle opens
import { enableFlashPushTracking } from '@flash-analytics/react-native';
enableFlashPushTracking({
appId: process.env.FLASH_APP_ID!,
android: {
defaultChannelId: 'default',
defaultNotificationTtlMs: 5 * 60 * 1000, // 5 min TTL for expiry inference
},
});
// Track when the user taps a notification (works from JS)
await analytics.trackNotificationOpened({
notificationId: payload.notificationId,
messageId: payload.messageId,
provider: 'firebase',
});iOS — Notification Service Extension (delivery + expiry)
In Xcode: File → New → Target → Notification Service Extension. Your push payload must include "mutable-content": 1.
import UserNotifications
import FlashAnalytics
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
var extensionAnalytics: FlashAnalytics?
override func didReceive(
_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
) {
self.contentHandler = contentHandler
bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent
extensionAnalytics = FlashAnalytics.configureShared(
options: FlashAnalyticsOptions(
appId: "YOUR_APP_ID",
endpoint: "https://api.flashanalytics.app",
capturePushLifecycle: true
)
)
// Tracks notification_delivered
extensionAnalytics?.trackNotificationDelivered(request: request)
contentHandler(bestAttemptContent ?? request.content)
}
override func serviceExtensionTimeWillExpire() {
// Tracks notification_expired
if let content = bestAttemptContent {
extensionAnalytics?.trackNotificationExpired(content: content)
}
contentHandler?(bestAttemptContent ?? UNNotificationContent())
}
}Also set UNUserNotificationCenterDelegate in your AppDelegate and call FlashAnalytics.shared.trackNotificationResponse(response) inside didReceive(_:withCompletionHandler:) to capture open, dismiss, and action events. See the Swift SDK push section for the full delegate code.
Android — BroadcastReceiver (dismiss + action)
notification_expired from the payload's expiresAt field or defaultNotificationTtlMs.import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import app.flashanalytics.sdk.FlashNotificationEvent
import app.flashanalytics.sdk.android.FlashAnalyticsAndroid
class FlashNotificationReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val analytics = FlashAnalyticsAndroid.getInstance()
val event = when (intent.action) {
"FLASH_NOTIFICATION_DISMISSED" -> FlashNotificationEvent.DISMISSED
"FLASH_NOTIFICATION_ACTION" -> FlashNotificationEvent.ACTION_CLICKED
else -> return
}
analytics.trackNotificationEvent(
event = event,
intent = intent,
source = "android_broadcast_receiver",
appState = "background",
)
}
}<receiver
android:name=".FlashNotificationReceiver"
android:exported="false">
<intent-filter>
<action android:name="FLASH_NOTIFICATION_DISMISSED" />
<action android:name="FLASH_NOTIFICATION_ACTION" />
</intent-filter>
</receiver>When building your notification with NotificationCompat.Builder, attach a PendingIntent pointing at FlashNotificationReceiver for the delete action (dismiss) and each action button.
val dismissIntent = Intent(context, FlashNotificationReceiver::class.java).apply {
action = "FLASH_NOTIFICATION_DISMISSED"
putExtra("notificationId", notifId)
putExtra("provider", "firebase")
}
val dismissPi = PendingIntent.getBroadcast(
context, 0, dismissIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle(title)
.setContentText(body)
.setDeleteIntent(dismissPi) // fires DISMISSED when swiped away
.build()Session access
By default the SDK sends a lightweight sdk_initialized event when it is constructed. That lets the backend create and return a session before app lifecycle, install, update, and foreground events are sent. Use onSessionUpdated if your app needs that session as soon as the startup response returns. Set captureSessionOnInit: false only if you do not want that startup event.
const analytics = new FlashAnalytics({
appId: process.env.FLASH_APP_ID!,
captureSessionOnInit: true,
onSessionUpdated(session) {
console.log(session.id);
console.log(session.estimatedExpiresAt);
console.log(session.estimatedTtlMs);
},
});
const session = analytics.getSession();
console.log(session?.id);
console.log(session?.estimatedExpiresAt);
console.log(session?.estimatedTtlMs);const analytics = new FlashAnalytics({
appId: process.env.FLASH_APP_ID!,
captureSessionOnInit: false,
});Batching
React Native supports the same deliveryStrategy batching API as the core SDK. The SDK flushes by queue size, by timeout, and when the app moves to background or inactive state. Batch responses update getSession() and trigger onSessionUpdated.
const analytics = new FlashAnalytics({
appId: process.env.FLASH_APP_ID!,
deliveryStrategy: {
strategy: 'batching',
config: {
size: 10, // flush after 10 queued events
timeout: 5000, // or every 5 seconds
fallbackToIndividual: true, // retry individually if batch fails
},
},
});
// Flush manually before a critical transition if needed
await analytics.flushBatch();@react-native-async-storage/async-storage is installed, queued batch events are persisted so they can survive app force-kills. If it is not installed, batching still works in memory for the active process.