On this page
📊 Analytics System
Index
Analytics Options
Expo Analytics
Firebase Analytics
Mixpanel
PostHog
Analytics Context
Standard Events
Best Practices
Analytics Options
Option Complexity Best for Pros Cons Firebase Analytics Low Production apps Free, robust, integrated Google ecosystem Mixpanel Medium Product analytics Funnels, retention Costly at scale PostHog Medium Open source, privacy Self-host available More setup Amplitude Medium Growth teams Very complete Costly Custom Backend High Full control No dependencies More development
Expo Analytics
Instalación
npx expo install expo-application expo-constants
Basic Implementation
import * as Application from 'expo-application' ;
import Constants from 'expo-constants' ;
import { Platform } from 'react-native' ;
import AsyncStorage from '@react-native-async-storage/async-storage' ;
interface AnalyticsEvent {
name: string ;
properties? : Record< string , unknown > ;
timestamp: string ;
sessionId: string ;
userId? : string ;
}
const EVENTS_KEY = 'analytics_events' ;
const SESSION_KEY = 'analytics_session' ;
let sessionId: string | null = null ;
let userId: string | null = null ;
export async function initAnalytics ( ) {
sessionId = ` session_ ${ Date. now ( ) } _ ${ Math. random ( ) . toString ( 36 ) . slice ( 2 ) } ` ;
await AsyncStorage. setItem ( SESSION_KEY , sessionId) ;
}
export function setUserId ( id: string | null ) {
userId = id;
}
export async function trackEvent (
name: string ,
properties? : Record< string , unknown >
) {
const event: AnalyticsEvent = {
name,
properties: {
... properties,
platform: Platform. OS ,
appVersion: Application. nativeApplicationVersion,
buildVersion: Application. nativeBuildVersion,
} ,
timestamp: new Date ( ) . toISOString ( ) ,
sessionId: sessionId || 'unknown' ,
userId: userId || undefined ,
} ;
console . log ( '[Analytics]' , event. name, event. properties) ;
const stored = await AsyncStorage. getItem ( EVENTS_KEY ) ;
const events: AnalyticsEvent[ ] = stored ? JSON . parse ( stored) : [ ] ;
events. push ( event) ;
await AsyncStorage. setItem ( EVENTS_KEY , JSON . stringify ( events) ) ;
if ( events. length >= 10 ) {
await flushEvents ( ) ;
}
}
export async function flushEvents ( ) {
const stored = await AsyncStorage. getItem ( EVENTS_KEY ) ;
if ( ! stored) return ;
const events: AnalyticsEvent[ ] = JSON . parse ( stored) ;
if ( events. length === 0 ) return ;
try {
await AsyncStorage. setItem ( EVENTS_KEY , '[]' ) ;
} catch ( error) {
console . error ( 'Failed to flush analytics:' , error) ;
}
}
export async function trackScreen ( screenName: string ) {
await trackEvent ( 'screen_view' , { screen_name: screenName } ) ;
}
Firebase Analytics
Instalación
npx expo install @react-native-firebase/app @react-native-firebase/analytics
Configuración
import analytics from '@react-native-firebase/analytics' ;
export const firebaseAnalytics = {
async logEvent ( name: string , params? : Record< string , unknown > ) {
await analytics ( ) . logEvent ( name, params) ;
} ,
async logScreenView ( screenName: string , screenClass? : string ) {
await analytics ( ) . logScreenView ( {
screen_name: screenName,
screen_class: screenClass || screenName,
} ) ;
} ,
async setUserId ( userId: string | null ) {
await analytics ( ) . setUserId ( userId) ;
} ,
async setUserProperties ( properties: Record< string , string > ) {
for ( const [ key, value] of Object. entries ( properties) ) {
await analytics ( ) . setUserProperty ( key, value) ;
}
} ,
async logLogin ( method: string ) {
await analytics ( ) . logLogin ( { method } ) ;
} ,
async logSignUp ( method: string ) {
await analytics ( ) . logSignUp ( { method } ) ;
} ,
async logPurchase ( params: {
currency: string ;
value: number ;
items? : Array < { item_id: string ; item_name: string ; price: number } > ;
} ) {
await analytics ( ) . logPurchase ( params) ;
} ,
async logAddToCart ( params: {
currency: string ;
value: number ;
items: Array < { item_id: string ; item_name: string ; price: number } > ;
} ) {
await analytics ( ) . logAddToCart ( params) ;
} ,
async logShare ( params: { content_type: string ; item_id: string ; method: string } ) {
await analytics ( ) . logShare ( params) ;
} ,
async logSearch ( searchTerm: string ) {
await analytics ( ) . logSearch ( { search_term: searchTerm } ) ;
} ,
} ;
Mixpanel
Instalación
npx expo install mixpanel-react-native
Configuración
import { Mixpanel } from 'mixpanel-react-native' ;
const MIXPANEL_TOKEN = process. env. EXPO_PUBLIC_MIXPANEL_TOKEN ! ;
const mixpanel = new Mixpanel ( MIXPANEL_TOKEN , true ) ;
export async function initMixpanel ( ) {
await mixpanel. init ( ) ;
}
export const mixpanelAnalytics = {
track ( event: string , properties? : Record< string , unknown > ) {
mixpanel. track ( event, properties) ;
} ,
identify ( userId: string ) {
mixpanel. identify ( userId) ;
} ,
setProfile ( properties: Record< string , unknown > ) {
mixpanel. getPeople ( ) . set ( properties) ;
} ,
reset ( ) {
mixpanel. reset ( ) ;
} ,
timeEvent ( eventName: string ) {
mixpanel. timeEvent ( eventName) ;
} ,
trackWithGroups (
event: string ,
properties: Record< string , unknown > ,
groups: Record< string , string >
) {
mixpanel. trackWithGroups ( event, properties, groups) ;
} ,
setGroup ( groupKey: string , groupId: string ) {
mixpanel. setGroup ( groupKey, groupId) ;
} ,
registerSuperProperties ( properties: Record< string , unknown > ) {
mixpanel. registerSuperProperties ( properties) ;
} ,
} ;
PostHog
Instalación
npx expo install posthog-react-native
Configuración
import PostHog from 'posthog-react-native' ;
const POSTHOG_API_KEY = process. env. EXPO_PUBLIC_POSTHOG_API_KEY ! ;
const POSTHOG_HOST = process. env. EXPO_PUBLIC_POSTHOG_HOST || 'https://app.posthog.com' ;
let posthog: PostHog | null = null ;
export async function initPostHog ( ) {
posthog = await PostHog. initAsync ( POSTHOG_API_KEY , {
host: POSTHOG_HOST ,
} ) ;
}
export const posthogAnalytics = {
capture ( event: string , properties? : Record< string , unknown > ) {
posthog?. capture ( event, properties) ;
} ,
screen ( screenName: string , properties? : Record< string , unknown > ) {
posthog?. screen ( screenName, properties) ;
} ,
identify ( userId: string , properties? : Record< string , unknown > ) {
posthog?. identify ( userId, properties) ;
} ,
alias ( alias: string ) {
posthog?. alias ( alias) ;
} ,
reset ( ) {
posthog?. reset ( ) ;
} ,
isFeatureEnabled ( key: string ) : boolean {
return posthog?. isFeatureEnabled ( key) ?? false ;
} ,
getFeatureFlag ( key: string ) : string | boolean | undefined {
return posthog?. getFeatureFlag ( key) ;
} ,
reloadFeatureFlags ( ) {
posthog?. reloadFeatureFlagsAsync ( ) ;
} ,
flush ( ) {
posthog?. flush ( ) ;
} ,
} ;
Analytics Context
import createContextHook from '@nkzw/create-context-hook' ;
import { useEffect, useCallback, useRef } from 'react' ;
import { AppState, AppStateStatus, Platform } from 'react-native' ;
import * as Application from 'expo-application' ;
import AsyncStorage from '@react-native-async-storage/async-storage' ;
type AnalyticsProvider = 'firebase' | 'mixpanel' | 'posthog' | 'custom' ;
interface AnalyticsEvent {
name: string ;
properties? : Record< string , unknown > ;
timestamp: string ;
}
const EVENTS_KEY = 'analytics_queue' ;
const USER_ID_KEY = 'analytics_user_id' ;
export const [ AnalyticsProvider, useAnalytics] = createContextHook ( ( ) => {
const sessionStart = useRef ( Date. now ( ) ) ;
const userId = useRef < string | null > ( null ) ;
const eventsQueue = useRef < AnalyticsEvent[ ] > ( [ ] ) ;
useEffect ( ( ) => {
AsyncStorage. getItem ( USER_ID_KEY ) . then ( ( id) => {
userId. current = id;
} ) ;
const subscription = AppState. addEventListener ( 'change' , handleAppStateChange) ;
track ( 'session_start' , {
platform: Platform. OS ,
app_version: Application. nativeApplicationVersion,
} ) ;
return ( ) => {
subscription. remove ( ) ;
flush ( ) ;
} ;
} , [ ] ) ;
const handleAppStateChange = ( state: AppStateStatus) => {
if ( state === 'background' ) {
const duration = Math. round ( ( Date. now ( ) - sessionStart. current) / 1000 ) ;
track ( 'session_end' , { duration_seconds: duration } ) ;
flush ( ) ;
} else if ( state === 'active' ) {
sessionStart. current = Date. now ( ) ;
track ( 'session_start' ) ;
}
} ;
const track = useCallback ( ( name: string , properties? : Record< string , unknown > ) => {
const event: AnalyticsEvent = {
name,
properties: {
... properties,
user_id: userId. current,
platform: Platform. OS ,
timestamp: new Date ( ) . toISOString ( ) ,
} ,
timestamp: new Date ( ) . toISOString ( ) ,
} ;
console . log ( '[Analytics]' , name, properties) ;
eventsQueue. current. push ( event) ;
if ( eventsQueue. current. length >= 10 ) {
flush ( ) ;
}
} , [ ] ) ;
const trackScreen = useCallback ( ( screenName: string , properties? : Record< string , unknown > ) => {
track ( 'screen_view' , { screen_name: screenName, ... properties } ) ;
} , [ track] ) ;
const identify = useCallback ( async ( id: string , traits? : Record< string , unknown > ) => {
userId. current = id;
await AsyncStorage. setItem ( USER_ID_KEY , id) ;
track ( 'identify' , { user_id: id, ... traits } ) ;
} , [ track] ) ;
const reset = useCallback ( async ( ) => {
userId. current = null ;
await AsyncStorage. removeItem ( USER_ID_KEY ) ;
track ( 'logout' ) ;
} , [ track] ) ;
const flush = useCallback ( async ( ) => {
if ( eventsQueue. current. length === 0 ) return ;
const events = [ ... eventsQueue. current] ;
eventsQueue. current = [ ] ;
try {
console . log ( '[Analytics] Flushed' , events. length, 'events' ) ;
} catch ( error) {
console . error ( '[Analytics] Flush failed:' , error) ;
eventsQueue. current = [ ... events, ... eventsQueue. current] ;
}
} , [ ] ) ;
const trackPurchase = useCallback ( ( params: {
productId: string ;
price: number ;
currency: string ;
source? : string ;
} ) => {
track ( 'purchase' , {
product_id: params. productId,
price: params. price,
currency: params. currency,
source: params. source,
} ) ;
} , [ track] ) ;
const trackError = useCallback ( ( error: Error, context? : Record< string , unknown > ) => {
track ( 'error' , {
error_message: error. message,
error_name: error. name,
error_stack: error. stack?. slice ( 0 , 500 ) ,
... context,
} ) ;
} , [ track] ) ;
return {
track,
trackScreen,
trackPurchase,
trackError,
identify,
reset,
flush,
} ;
} ) ;
Standard Events
User Events
track ( 'sign_up' , { method: 'email' | 'google' | 'apple' } ) ;
track ( 'login' , { method: 'email' | 'google' | 'apple' } ) ;
track ( 'logout' ) ;
track ( 'password_reset_requested' ) ;
track ( 'profile_updated' , { fields: [ 'name' , 'avatar' ] } ) ;
track ( 'settings_changed' , { setting: 'notifications' , value: true } ) ;
Navigation Events
trackScreen ( 'Home' ) ;
trackScreen ( 'ProductDetail' , { product_id: '123' } ) ;
track ( 'tab_changed' , { from: 'home' , to: 'explore' } ) ;
track ( 'modal_opened' , { modal: 'filters' } ) ;
Engagement Events
track ( 'item_viewed' , { item_id: '123' , category: 'tech' } ) ;
track ( 'item_favorited' , { item_id: '123' } ) ;
track ( 'item_shared' , { item_id: '123' , method: 'copy_link' } ) ;
track ( 'search' , { query: 'react native' , results_count: 15 } ) ;
track ( 'filter_applied' , { filters: { category: 'tech' , price: 'free' } } ) ;
Monetization Events
track ( 'paywall_viewed' , { source: 'feature_gate' } ) ;
track ( 'subscription_started' , { plan: 'premium_monthly' , price: 9.99 } ) ;
track ( 'subscription_cancelled' , { plan: 'premium_monthly' , reason: 'too_expensive' } ) ;
track ( 'purchase_completed' , { product_id: 'credits_100' , price: 4.99 } ) ;
Error Events
trackError ( new Error ( 'API call failed' ) , { endpoint: '/api/items' } ) ;
track ( 'error_displayed' , { error_type: 'network' , screen: 'Home' } ) ;
Best Practices
1. Naming Conventions
track ( 'button_clicked' , { button_name: 'submit' } ) ;
track ( 'form_submitted' , { form_name: 'contact' } ) ;
track ( 'Click' , { btn: 'sub' } ) ;
track ( 'FormSubmit' ) ;
2. Consistent Properties
interface StandardProperties {
screen_name? : string ;
user_id? : string ;
session_id? : string ;
timestamp? : string ;
}
track ( 'item_purchased' , {
item_id: '123' ,
item_name: 'Premium Plan' ,
price: 9.99 ,
currency: 'USD' ,
screen_name: 'Paywall' ,
} ) ;
3. Avoid Over-Tracking
onChangeText= { ( text) => track ( 'text_input' , { text } ) }
onSubmit= { ( ) => track ( 'search_submitted' , { query } ) }
4. Privacy First
track ( 'user_profile' , { email: 'user@email.com' , phone: '+1234567890' } ) ;
track ( 'user_profile' , { user_id: 'usr_abc123' } ) ;
5. Hook for Automatic Screen Tracking
import { useEffect } from 'react' ;
import { usePathname } from 'expo-router' ;
import { useAnalytics } from '@/contexts/AnalyticsContext' ;
export function useScreenTracking ( ) {
const pathname = usePathname ( ) ;
const { trackScreen } = useAnalytics ( ) ;
useEffect ( ( ) => {
const screenName = pathname
. replace ( / \/ / g , '_' )
. replace ( / ^ \( tabs\) _? / , '' )
. replace ( / ^ _/ , '' ) || 'Home' ;
trackScreen ( screenName) ;
} , [ pathname, trackScreen] ) ;
}
Implementation Checklist