react-native-sop-docs/11-post-release-maintenance/analytics.md

14 KiB

Analytics Setup

Firebase Analytics

Installation

yarn add @react-native-firebase/app
yarn add @react-native-firebase/analytics

Configuration

analytics.config.js:

import analytics from '@react-native-firebase/analytics';
import Config from 'react-native-config';

class AnalyticsService {
  static async initialize() {
    if (__DEV__) {
      // Disable analytics in development
      await analytics().setAnalyticsCollectionEnabled(false);
    } else {
      await analytics().setAnalyticsCollectionEnabled(true);
    }
  }

  static async setUserId(userId: string) {
    await analytics().setUserId(userId);
  }

  static async setUserProperties(properties: Record<string, string>) {
    for (const [key, value] of Object.entries(properties)) {
      await analytics().setUserProperty(key, value);
    }
  }

  static async logEvent(eventName: string, parameters?: Record<string, any>) {
    try {
      await analytics().logEvent(eventName, parameters);
      console.log(`Analytics: ${eventName}`, parameters);
    } catch (error) {
      console.error('Analytics error:', error);
    }
  }

  static async logScreenView(screenName: string, screenClass?: string) {
    await this.logEvent('screen_view', {
      screen_name: screenName,
      screen_class: screenClass || screenName,
    });
  }

  static async logLogin(method: string) {
    await this.logEvent('login', {
      method,
    });
  }

  static async logSignUp(method: string) {
    await this.logEvent('sign_up', {
      method,
    });
  }

  static async logPurchase(transactionId: string, value: number, currency: string = 'USD') {
    await this.logEvent('purchase', {
      transaction_id: transactionId,
      value,
      currency,
    });
  }

  static async logShare(contentType: string, itemId: string) {
    await this.logEvent('share', {
      content_type: contentType,
      item_id: itemId,
    });
  }

  static async logSearch(searchTerm: string) {
    await this.logEvent('search', {
      search_term: searchTerm,
    });
  }
}

export default AnalyticsService;

Custom Event Tracking

User Journey Tracking

// src/utils/userJourneyTracker.ts
import AnalyticsService from '@config/analytics.config';

export class UserJourneyTracker {
  private static sessionStartTime: number = Date.now();
  private static currentScreen: string = '';

  static startSession() {
    this.sessionStartTime = Date.now();
    AnalyticsService.logEvent('session_start', {
      timestamp: this.sessionStartTime,
    });
  }

  static endSession() {
    const sessionDuration = Date.now() - this.sessionStartTime;
    AnalyticsService.logEvent('session_end', {
      session_duration: sessionDuration,
    });
  }

  static trackScreenView(screenName: string) {
    const previousScreen = this.currentScreen;
    this.currentScreen = screenName;

    AnalyticsService.logScreenView(screenName);

    if (previousScreen) {
      AnalyticsService.logEvent('screen_transition', {
        from_screen: previousScreen,
        to_screen: screenName,
      });
    }
  }

  static trackUserAction(action: string, context?: Record<string, any>) {
    AnalyticsService.logEvent('user_action', {
      action,
      screen: this.currentScreen,
      ...context,
    });
  }

  static trackFeatureUsage(feature: string, parameters?: Record<string, any>) {
    AnalyticsService.logEvent('feature_usage', {
      feature_name: feature,
      screen: this.currentScreen,
      ...parameters,
    });
  }

  static trackError(error: string, context?: Record<string, any>) {
    AnalyticsService.logEvent('app_error', {
      error_message: error,
      screen: this.currentScreen,
      ...context,
    });
  }
}

Donation Tracking

// src/utils/donationTracker.ts
import AnalyticsService from '@config/analytics.config';

export class DonationTracker {
  static trackDonationStart(requestId: string, amount: number) {
    AnalyticsService.logEvent('donation_start', {
      request_id: requestId,
      amount,
      currency: 'USD',
    });
  }

  static trackDonationComplete(
    requestId: string,
    amount: number,
    paymentMethod: string,
    transactionId: string
  ) {
    AnalyticsService.logEvent('donation_complete', {
      request_id: requestId,
      amount,
      currency: 'USD',
      payment_method: paymentMethod,
      transaction_id: transactionId,
    });

    // Also log as purchase for e-commerce tracking
    AnalyticsService.logPurchase(transactionId, amount);
  }

  static trackDonationFailed(
    requestId: string,
    amount: number,
    errorReason: string
  ) {
    AnalyticsService.logEvent('donation_failed', {
      request_id: requestId,
      amount,
      currency: 'USD',
      error_reason: errorReason,
    });
  }

  static trackRequestCreated(
    requestId: string,
    category: string,
    targetAmount: number
  ) {
    AnalyticsService.logEvent('request_created', {
      request_id: requestId,
      category,
      target_amount: targetAmount,
      currency: 'USD',
    });
  }

  static trackRequestViewed(requestId: string, category: string) {
    AnalyticsService.logEvent('request_viewed', {
      request_id: requestId,
      category,
    });
  }
}

Performance Analytics

App Performance Monitoring

// src/utils/performanceAnalytics.ts
import AnalyticsService from '@config/analytics.config';

export class PerformanceAnalytics {
  private static metrics: Map<string, number> = new Map();

  static startTiming(operation: string) {
    this.metrics.set(operation, Date.now());
  }

  static endTiming(operation: string) {
    const startTime = this.metrics.get(operation);
    if (!startTime) return;

    const duration = Date.now() - startTime;
    this.metrics.delete(operation);

    AnalyticsService.logEvent('performance_timing', {
      operation,
      duration,
    });

    // Track slow operations
    if (duration > 3000) {
      AnalyticsService.logEvent('slow_operation', {
        operation,
        duration,
      });
    }
  }

  static trackAppLaunchTime(launchTime: number) {
    AnalyticsService.logEvent('app_launch_time', {
      launch_time: launchTime,
    });
  }

  static trackAPIResponse(endpoint: string, duration: number, status: number) {
    AnalyticsService.logEvent('api_response', {
      endpoint,
      duration,
      status,
    });
  }

  static trackMemoryUsage(usedMemory: number, totalMemory: number) {
    AnalyticsService.logEvent('memory_usage', {
      used_memory: usedMemory,
      total_memory: totalMemory,
      usage_percentage: (usedMemory / totalMemory) * 100,
    });
  }
}

User Behavior Analytics

Engagement Tracking

// src/utils/engagementTracker.ts
import AnalyticsService from '@config/analytics.config';

export class EngagementTracker {
  private static sessionEvents: string[] = [];

  static trackAppOpen() {
    AnalyticsService.logEvent('app_open');
    this.sessionEvents = [];
  }

  static trackAppBackground() {
    AnalyticsService.logEvent('app_background', {
      session_events: this.sessionEvents.length,
      events: this.sessionEvents.slice(-10), // Last 10 events
    });
  }

  static trackUserEngagement(action: string, value?: number) {
    this.sessionEvents.push(action);
    
    AnalyticsService.logEvent('user_engagement', {
      engagement_time_msec: value || 1000,
      action,
    });
  }

  static trackContentInteraction(
    contentType: string,
    contentId: string,
    action: string
  ) {
    AnalyticsService.logEvent('content_interaction', {
      content_type: contentType,
      content_id: contentId,
      action,
    });
  }

  static trackSocialShare(platform: string, contentType: string) {
    AnalyticsService.logEvent('social_share', {
      platform,
      content_type: contentType,
    });
  }

  static trackSearchUsage(query: string, resultsCount: number) {
    AnalyticsService.logEvent('search_usage', {
      search_term: query,
      results_count: resultsCount,
    });
  }
}

A/B Testing Integration

Feature Flag Analytics

// src/utils/abTestingTracker.ts
import AnalyticsService from '@config/analytics.config';

export class ABTestingTracker {
  static trackExperimentExposure(
    experimentId: string,
    variantId: string,
    userId: string
  ) {
    AnalyticsService.logEvent('experiment_exposure', {
      experiment_id: experimentId,
      variant_id: variantId,
      user_id: userId,
    });
  }

  static trackExperimentConversion(
    experimentId: string,
    variantId: string,
    conversionType: string,
    value?: number
  ) {
    AnalyticsService.logEvent('experiment_conversion', {
      experiment_id: experimentId,
      variant_id: variantId,
      conversion_type: conversionType,
      value,
    });
  }

  static trackFeatureFlagUsage(flagName: string, enabled: boolean) {
    AnalyticsService.logEvent('feature_flag_usage', {
      flag_name: flagName,
      enabled,
    });
  }
}

Custom Dashboards

Analytics Dashboard Data

// src/utils/analyticsDashboard.ts
import AnalyticsService from '@config/analytics.config';

export class AnalyticsDashboard {
  static trackKPI(kpiName: string, value: number, unit?: string) {
    AnalyticsService.logEvent('kpi_metric', {
      kpi_name: kpiName,
      value,
      unit: unit || 'count',
      timestamp: Date.now(),
    });
  }

  static trackBusinessMetric(
    metricName: string,
    value: number,
    category: string
  ) {
    AnalyticsService.logEvent('business_metric', {
      metric_name: metricName,
      value,
      category,
      timestamp: Date.now(),
    });
  }

  static trackUserRetention(daysSinceInstall: number, isActive: boolean) {
    AnalyticsService.logEvent('user_retention', {
      days_since_install: daysSinceInstall,
      is_active: isActive,
    });
  }

  static trackFunnelStep(funnelName: string, step: string, completed: boolean) {
    AnalyticsService.logEvent('funnel_step', {
      funnel_name: funnelName,
      step,
      completed,
    });
  }
}

Privacy Compliance

GDPR/CCPA Compliance

// src/utils/privacyCompliance.ts
import AnalyticsService from '@config/analytics.config';
import AsyncStorage from '@react-native-async-storage/async-storage';

export class PrivacyCompliance {
  private static readonly CONSENT_KEY = 'analytics_consent';

  static async hasUserConsent(): Promise<boolean> {
    try {
      const consent = await AsyncStorage.getItem(this.CONSENT_KEY);
      return consent === 'true';
    } catch {
      return false;
    }
  }

  static async setUserConsent(hasConsent: boolean) {
    try {
      await AsyncStorage.setItem(this.CONSENT_KEY, hasConsent.toString());
      
      if (hasConsent) {
        await AnalyticsService.initialize();
        AnalyticsService.logEvent('analytics_consent_granted');
      } else {
        await AnalyticsService.setAnalyticsCollectionEnabled(false);
      }
    } catch (error) {
      console.error('Error setting analytics consent:', error);
    }
  }

  static async revokeConsent() {
    await this.setUserConsent(false);
    await AsyncStorage.removeItem(this.CONSENT_KEY);
    
    // Clear any stored analytics data
    AnalyticsService.logEvent('analytics_consent_revoked');
  }

  static trackConsentRequest(source: string) {
    AnalyticsService.logEvent('consent_request_shown', {
      source,
    });
  }

  static trackConsentResponse(granted: boolean, source: string) {
    AnalyticsService.logEvent('consent_response', {
      granted,
      source,
    });
  }
}

Analytics Integration

Navigation Analytics

// src/navigation/AnalyticsNavigationContainer.tsx
import React from 'react';
import {NavigationContainer, NavigationState} from '@react-navigation/native';
import {UserJourneyTracker} from '@utils/userJourneyTracker';

interface Props {
  children: React.ReactNode;
}

export const AnalyticsNavigationContainer: React.FC<Props> = ({children}) => {
  const routeNameRef = React.useRef<string>();

  const onStateChange = (state: NavigationState | undefined) => {
    const previousRouteName = routeNameRef.current;
    const currentRouteName = getActiveRouteName(state);

    if (previousRouteName !== currentRouteName) {
      UserJourneyTracker.trackScreenView(currentRouteName || 'Unknown');
    }

    routeNameRef.current = currentRouteName;
  };

  return (
    <NavigationContainer onStateChange={onStateChange}>
      {children}
    </NavigationContainer>
  );
};

function getActiveRouteName(state: NavigationState | undefined): string | undefined {
  if (!state) return undefined;

  const route = state.routes[state.index];

  if (route.state) {
    return getActiveRouteName(route.state as NavigationState);
  }

  return route.name;
}

Component Analytics HOC

// src/hoc/withAnalytics.tsx
import React, {useEffect} from 'react';
import {UserJourneyTracker} from '@utils/userJourneyTracker';

interface AnalyticsProps {
  screenName?: string;
  trackProps?: string[];
}

export function withAnalytics<P extends object>(
  WrappedComponent: React.ComponentType<P>,
  analyticsProps: AnalyticsProps
) {
  return function AnalyticsComponent(props: P) {
    useEffect(() => {
      if (analyticsProps.screenName) {
        UserJourneyTracker.trackScreenView(analyticsProps.screenName);
      }

      // Track specific props if specified
      if (analyticsProps.trackProps) {
        const trackedData = analyticsProps.trackProps.reduce((acc, propName) => {
          if (propName in props) {
            acc[propName] = (props as any)[propName];
          }
          return acc;
        }, {} as Record<string, any>);

        if (Object.keys(trackedData).length > 0) {
          UserJourneyTracker.trackUserAction('component_rendered', trackedData);
        }
      }
    }, []);

    return <WrappedComponent {...props} />;
  };
}

// Usage example:
// export default withAnalytics(MyComponent, {
//   screenName: 'MyScreen',
//   trackProps: ['userId', 'category']
// });