Complete guide for using @stickyqr/analytics with React Native and Expo 54.
# Install the analytics SDK
npx expo install @stickyqr/analytics
# Install required peer dependencies
npx expo install @react-native-async-storage/async-storage expo-constants expo-device react-native-get-random-values# Install the analytics SDK
npm install @stickyqr/analytics
# Install peer dependencies
npm install @react-native-async-storage/async-storage
npm install expo-constants expo-device
npm install react-native-get-random-values
# Link native modules (if not using autolinking)
cd ios && pod install && cd ..// lib/analytics.ts
import 'react-native-get-random-values';
import { Analytics } from '@stickyqr/analytics';
export const analytics = new Analytics({
writeKey: 'your-write-key',
debug: __DEV__, // Enable debug in development
trackPageViews: false, // No page tracking in RN
flushAt: 10,
flushInterval: 30000 // 30 seconds
});// contexts/AnalyticsContext.tsx
import 'react-native-get-random-values';
import React, { createContext, useContext, useEffect, useState } from 'react';
import { Analytics } from '@stickyqr/analytics';
const AnalyticsContext = createContext<Analytics | null>(null);
export function AnalyticsProvider({
children,
writeKey
}: {
children: React.ReactNode;
writeKey: string;
}) {
const [analytics] = useState(() => {
return new Analytics({
writeKey,
debug: __DEV__
});
});
useEffect(() => {
// Track app launch
analytics.track('App Launched');
return () => {
analytics.flush();
};
}, [analytics]);
return (
<AnalyticsContext.Provider value={analytics}>
{children}
</AnalyticsContext.Provider>
);
}
export function useAnalytics() {
const context = useContext(AnalyticsContext);
if (!context) {
throw new Error('useAnalytics must be used within AnalyticsProvider');
}
return context;
}// App.tsx
import { AnalyticsProvider } from './contexts/AnalyticsContext';
export default function App() {
return (
<AnalyticsProvider writeKey="your-write-key">
{/* Your app components */}
</AnalyticsProvider>
);
}import { useAnalytics } from './contexts/AnalyticsContext';
function MyScreen() {
const analytics = useAnalytics();
const handleButtonClick = () => {
analytics.track('Button Clicked', {
buttonId: 'signup-cta',
screen: 'Home'
});
};
return <Button onPress={handleButtonClick} title="Sign Up" />;
}function LoginScreen() {
const analytics = useAnalytics();
const handleLogin = async (userId: string, email: string) => {
await analytics.identify(userId, {
email,
name: 'John Doe',
platform: 'mobile'
});
analytics.track('Login Successful');
};
return <LoginForm onSubmit={handleLogin} />;
}import { useEffect } from 'react';
import { useAnalytics } from './contexts/AnalyticsContext';
// Custom hook for screen tracking
function useScreenTracking(screenName: string, properties?: any) {
const analytics = useAnalytics();
useEffect(() => {
analytics.screen(screenName, 'App', properties);
}, [analytics, screenName]);
}
// Usage in screen component
function ProductScreen({ route }: any) {
const { productId } = route.params;
useScreenTracking('Product', {
productId,
source: 'catalog'
});
return <View>{/* Screen content */}</View>;
}function ProductScreen({ product }: any) {
const analytics = useAnalytics();
const handleAddToCart = () => {
analytics.track('Product Added', {
productId: product.id,
name: product.name,
price: product.price,
quantity: 1,
currency: 'USD'
});
};
const handlePurchase = (orderId: string) => {
analytics.track('Order Completed', {
orderId,
revenue: product.price,
currency: 'USD',
products: [
{
productId: product.id,
name: product.name,
price: product.price,
quantity: 1
}
]
});
};
return (
<View>
<Button title="Add to Cart" onPress={handleAddToCart} />
<Button title="Buy Now" onPress={() => handlePurchase('order-123')} />
</View>
);
}function SettingsScreen() {
const analytics = useAnalytics();
const handleLogout = async () => {
analytics.track('User Logged Out');
await analytics.reset(); // Clear user data, preserve deviceId
};
return <Button title="Logout" onPress={handleLogout} />;
}Automatic screen tracking with React Navigation:
// App.tsx
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { useRef } from 'react';
import { useAnalytics } from './contexts/AnalyticsContext';
const Stack = createNativeStackNavigator();
function AppNavigator() {
const analytics = useAnalytics();
const routeNameRef = useRef<string>();
const navigationRef = useRef();
return (
<NavigationContainer
ref={navigationRef}
onReady={() => {
routeNameRef.current = navigationRef.current?.getCurrentRoute()?.name;
}}
onStateChange={() => {
const previousRouteName = routeNameRef.current;
const currentRoute = navigationRef.current?.getCurrentRoute();
const currentRouteName = currentRoute?.name;
if (previousRouteName !== currentRouteName) {
// Track screen view
analytics.screen(currentRouteName || 'Unknown', 'App', {
params: currentRoute?.params
});
}
routeNameRef.current = currentRouteName;
}}
>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Product" component={ProductScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
export default function App() {
return (
<AnalyticsProvider writeKey="your-write-key">
<AppNavigator />
</AnalyticsProvider>
);
}The SDK automatically collects device info if expo-device is installed:
- Device manufacturer (Apple, Samsung, etc.)
- Device model (iPhone 15 Pro, Galaxy S24, etc.)
- OS name and version (iOS 17.2, Android 14, etc.)
The SDK automatically collects app info if expo-constants is installed:
- App name
- App version
- Build number
- Bundle identifier
User data (user ID, traits, anonymous ID) is automatically persisted using @react-native-async-storage/async-storage.
The SDK also persists deviceId separately using the same storage layer.
Full configuration options:
const analytics = new Analytics({
// Required
writeKey: 'your-write-key',
// Queue settings
flushAt: 10, // Flush after 10 events (lower for mobile)
flushInterval: 30000, // Flush every 30 seconds
maxQueueSize: 50, // Max 50 events in queue
retryAttempts: 3,
// Features
debug: __DEV__, // Enable in development
trackPageViews: false, // No page views in RN
// Storage keys
anonymousIdKey: 'stickyqr_analytics_anonymous_id',
deviceIdKey: 'stickyqr_analytics_device_id',
userIdKey: 'stickyqr_analytics_user_id'
});UUIDv7 generation requires crypto.getRandomValues(). If your React Native runtime does not expose it yet, install react-native-get-random-values and import it before initializing the SDK:
import 'react-native-get-random-values';# Install AsyncStorage
npx expo install @react-native-async-storage/async-storage# Install Expo device and constants
npx expo install expo-device expo-constantsMake sure you have the correct types:
npm install --save-dev @types/react @types/react-native- Check network connection
- Enable debug mode:
debug: true - Check console for errors
- Verify API endpoint is reachable
- Check writeKey is correct
Reduce queue size:
const analytics = new Analytics({
writeKey: 'your-write-key',
maxQueueSize: 20, // Lower queue size
flushAt: 5, // Flush more frequently
flushInterval: 10000 // Flush every 10 seconds
});Always wrap your app with AnalyticsProvider for easy access across components.
Use the useScreenTracking hook or React Navigation integration for automatic tracking.
import { AppState } from 'react-native';
useEffect(() => {
const subscription = AppState.addEventListener('change', (nextAppState) => {
if (nextAppState === 'background') {
analytics.flush(); // Flush events before backgrounding
}
});
return () => subscription.remove();
}, []);try {
await analytics.identify(userId, traits);
} catch (error) {
console.error('Analytics error:', error);
// Don't block user flow
}Enable debug mode to see events in console:
const analytics = new Analytics({
writeKey: 'your-write-key',
debug: __DEV__ // Only in development
});See complete examples in:
examples/react-native-expo-example.tsx- Full Expo app example- Integration with React Navigation
- E-commerce tracking
- User authentication flow
- Bundle size: ~17KB (very light for mobile)
- Native dependencies: Only AsyncStorage (required)
- Memory usage: Minimal, queue auto-flushes
- Battery impact: Negligible, batches requests
The SDK respects user privacy:
- ✅ Data stored locally (AsyncStorage)
- ✅ User can be reset with
analytics.reset()without rotatingdeviceId - ✅ No tracking until initialized
- ✅ Full control over data sent
If you're migrating from Segment React Native SDK:
- Replace
@segment/analytics-react-nativewith@stickyqr/analytics - API is 95% compatible
- No changes needed to tracking calls
- Update initialization only
See MIGRATION_FROM_SEGMENT.md for detailed migration guide.
For issues or questions:
- GitHub: https://github.com/stickyqr/analytics
- Email: support@stickyqr.com
- Docs: See README.md
- React Native: >=0.70.0
- Expo: >=50.0.0 (tested with Expo 54)
- iOS: >=13.0
- Android: SDK >=23 (Android 6.0+)
MIT