Expo
Installation
Install the SDK
bun add phase-analyticsInstall Required Peer Dependencies
Phase Analytics requires the following Expo packages to function properly:
bunx expo install @react-native-async-storage/async-storage expo-application expo-device expo-localizationThese dependencies are required for the SDK to work correctly. The SDK uses:
@react-native-async-storage/async-storage- Local storage for the persistent offline queueexpo-application- App version collection (app_version)expo-device- Device information collectionexpo-localization- Locale and timezone detection
Optional packages:
bunx expo install @react-native-community/netinfo expo-router@react-native-community/netinfo- Offline detection. Without it, the SDK keeps sending and relies on retriesexpo-router- Required only for automatic screen tracking (trackNavigation)
Get Your API Key
- Sign in to Phase Dashboard
- Create a new project or select an existing one
- Open API Keys tab
- Copy your API Key (starts with
phase_)
Setup
Wrap your app with the PhaseProvider component in your root layout to initialize the SDK:
import { PhaseProvider } from 'phase-analytics/expo';
export default function RootLayout() {
return (
<PhaseProvider
apiKey="phase_xxx"
>
<Stack />
</PhaseProvider>
);
}The PhaseProvider only initializes the SDK. Call Phase.identify() to register the device and start a session. Events tracked before identify() runs are buffered and sent afterwards.
Configuration
The PhaseProvider component accepts the following props:
Prop
Type
Usage
Identify User
Call Phase.identify() once at startup. This registers the device and starts a session. Calls made before PhaseProvider mounts (for example from child effects) are queued and run after initialization.
import { Phase } from 'phase-analytics/expo';
import { useEffect } from 'react';
export default function App() {
useEffect(() => {
// Initialize analytics - no PII collected by default
Phase.identify();
}, []);
return <YourApp />;
}Privacy by default:
- No personal data is collected without explicit properties
- Device ID is auto-generated and stored locally
- Only technical metadata is collected (OS version, platform, locale)
Adding custom properties:
You can optionally attach user properties. Important: If you add PII (personally identifiable information), ensure you have proper user consent:
// After user login and consent
await Phase.identify({
user_id: '123',
plan: 'premium',
beta_tester: true
});
// ⚠️ If adding PII, get consent first
const hasConsent = await customGetUserConsent();
if (hasConsent) {
await Phase.identify({
email: '[email protected]',
name: 'John Doe'
});
}Properties must be primitives: string, number, boolean, or null.
Track Events
Track custom events with optional parameters. Events tracked before Phase.identify() runs are buffered in memory (up to 200 events, for up to one hour) and sent once the session exists. If more than 200 pile up, the oldest are dropped.
// Event without parameters
Phase.track('app_opened');
// Event with parameters
Phase.track('purchase_completed', {
amount: 99.99,
currency: 'USD',
product_id: 'premium_plan'
});Event naming rules:
- Alphanumeric characters, underscores (
_), hyphens (-), periods (.), forward slashes (/), and spaces - 1-256 characters
- Examples:
purchase,user.signup,payment/success,Button Clicked
Event parameters:
- Flat primitive object only
- Values must be
string,number,boolean, ornull - Max 32 keys
- Key max 32 characters
- String value max 256 characters
- Serialized payload max 8 KB
- Empty objects are normalized away
Automatic Screen Tracking
Screen tracking is off by default. Install expo-router and set trackNavigation to true in your root layout. The SDK uses Expo Router's navigation hooks to track screen changes automatically:
import { PhaseProvider } from 'phase-analytics/expo';
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<PhaseProvider
apiKey="phase_xxx"
trackNavigation={true}
>
<Stack />
</PhaseProvider>
);
}How it works:
- Wraps any Expo Router component (
Stack,Tabs,Drawer, etc.) - Automatically tracks screen views on route changes
- Screen names are derived from pathname
- Works with nested routes and dynamic segments
If expo-router is not installed, trackNavigation logs an error and no screens are tracked.
Manual Screen Tracking
Use Phase.trackScreen() when trackNavigation is off or you use a custom navigator. params follow the same rules as Phase.track().
Phase.trackScreen('/settings/profile');
Phase.trackScreen('/settings/profile', { tab: 'billing' });Flush
Events are sent automatically in batches (see Delivery), so you rarely need this. Phase.flush() sends queued data right away and resolves when the attempt finishes.
await Phase.flush();Reset
Use Phase.reset() to forget the current device, for example on logout. It ends the session and generates a new device ID. Data already queued for the previous device is still delivered. Call Phase.identify() again to start tracking.
await Phase.reset();
await Phase.identify();Type Reference
DeviceProperties
Custom user/device attributes passed to Phase.identify():
Prop
Type
EventParams
Event parameters passed to Phase.track() and Phase.trackScreen():
Prop
Type
How It Works
Delivery
Devices, sessions, events and heartbeats go through a persistent queue and are sent to the batch endpoint:
- Every ~10 seconds while data is queued, as soon as 20 items are queued, and when the app goes to the background
- Items leave the queue only after the server accepts them. Failed requests are retried with backoff and honor
Retry-After - Changing the API key clears data queued under the previous key
Offline Support
The queue survives app restarts. It is stored with @react-native-async-storage/async-storage and capped at ~1.5 MB, dropping the oldest sessions first when full.
With @react-native-community/netinfo installed, nothing is sent while the device is offline and only the latest heartbeat per session is kept. Queued data is sent when the connection is restored.
Privacy
- No personal data is collected by default
- Device IDs are generated locally and stored persistently
- Geolocation is resolved server-side from IP address (disable with properties)
- All data collection is optional via configuration
Performance
- Queued data is batched and sent asynchronously
- Failed requests retry with exponential backoff
- Maximum batch size: 50 items per request
- Offline queue capped at 1000 items / ~1.5 MB