React Native SDK - Uninstall Tracking

Uninstall Tracking

Track app uninstalls to measure user retention and optimize re-engagement campaigns by integrating push notification services with the Singular SDK.

Important: Google deprecated GCM APIs in April 2018. Use Firebase Cloud Messaging (FCM) for all Android uninstall tracking implementations.

Android Uninstall Tracking

Prerequisites

Before implementing uninstall tracking in your React Native app, configure your app in the Singular platform following the guide Setting Up Android Uninstall Tracking.


System Requirements

Uninstall tracking requires Firebase Cloud Messaging and specific device configurations.

FCM Requirements (source):

  • Android Version: Devices must run Android 4.1 (API 16) or higher
  • Google Play Services: Devices must have the Google Play Store app installed
  • Emulator Support: Android 4.1+ emulators with Google APIs are supported
  • Distribution: Apps can be distributed outside the Google Play Store while still supporting uninstall tracking

Note: Users on unsupported Android versions or devices without Google Play Services will not be tracked for uninstalls.


Implementation Steps

Step 1: Install Firebase Packages

Add the React Native Firebase dependencies for core functionality and messaging support.

bash
npm install @react-native-firebase/app
npm install @react-native-firebase/messaging

Step 2: Configure Firebase

Add Firebase configuration files to your React Native project for Android.

  1. Register your Android app in your Firebase Console project
  2. Download google-services.json and place it in android/app/
  3. Verify Firebase messaging dependencies are added to your project

For detailed setup instructions, see React Native Firebase Android Setup.


Step 3: Request Notification Permission

Request notification permissions from the user (required for Android 13+) before retrieving the FCM token.

JavaScript
import messaging from '@react-native-firebase/messaging';
import { Platform, PermissionsAndroid } from 'react-native';

async function requestNotificationPermission() {
  if (Platform.OS === 'android') {
    if (Platform.Version >= 33) {
      // Android 13+ requires explicit permission request
      const granted = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
      );
      return granted === PermissionsAndroid.RESULTS.GRANTED;
    }
    // Android 12 and below automatically have permission
    return true;
  }

  // iOS permission handled separately
  return true;
}

Step 4: Retrieve and Register FCM Token

Get the FCM device token and register it with Singular using setUninstallToken() after requesting permissions.

New ArchitectureOld Achitecture
// TurboModule direct API (React Native 0.76+ New Architecture)
import React, { useEffect } from 'react';
import NativeSingular from 'singular-react-native/js/NativeSingular';
import messaging from '@react-native-firebase/messaging';
import { Platform } from 'react-native';

export default function App() {
  useEffect(() => {
    if (Platform.OS === 'android') {
      initializeAndroidUninstallTracking();
    }
  }, []);

  async function initializeAndroidUninstallTracking() {
    try {
      // Request notification permission (Android 13+)
      const hasPermission = await requestNotificationPermission();

      if (!hasPermission) {
        console.warn('Notification permission denied - uninstall tracking unavailable');
        return;
      }

      // Get FCM token
      const token = await messaging().getToken();

      if (token) {
        // Register token with Singular for uninstall tracking
        NativeSingular.setUninstallToken(token);
        console.log('FCM token registered with Singular:', token);
      } else {
        console.warn('No FCM token available');
      }
    } catch (error) {
      console.error('Error setting up uninstall tracking:', error);
    }
  }

  async function requestNotificationPermission() {
    // Implementation from Step 3 above
    if (Platform.Version >= 33) {
      const { PermissionsAndroid } = require('react-native');
      const granted = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
      );
      return granted === PermissionsAndroid.RESULTS.GRANTED;
    }
    return true;
  }

  return (
    // Your app components
    null
  );
}

For complete method documentation, see setUninstallToken reference.


Step 5: Handle Token Refresh

Update the FCM token with Singular whenever it refreshes to maintain accurate uninstall tracking.

New ArchitectureOld Achitecture
// TurboModule direct API (React Native 0.76+ New Architecture)
import React, { useEffect } from 'react';
import NativeSingular from 'singular-react-native/js/NativeSingular';
import messaging from '@react-native-firebase/messaging';
import { Platform } from 'react-native';

export default function App() {
  useEffect(() => {
    if (Platform.OS === 'android') {
      // Set up token refresh listener
      const unsubscribe = messaging().onTokenRefresh((token) => {
        console.log('FCM token refreshed:', token);

        // Update Singular with new token
        NativeSingular.setUninstallToken(token);
      });

      // Clean up listener on unmount
      return () => unsubscribe();
    }
  }, []);

  return (
    // Your app components
    null
  );
}

Best Practice: FCM tokens can refresh at any time (app updates, device restore, etc.). Always subscribe to the onTokenRefresh event to keep Singular updated with the latest token.


iOS Uninstall Tracking

Prerequisites

Configure your iOS app in the Singular platform following the guide Setting Up iOS Uninstall Tracking.

Uninstall tracking on iOS is based on Apple Push Notification service (APNs) technology. If your app doesn't support push notifications, see Apple's guide to Registering Your App with APNs.


Implementation Steps

Step 1: Request iOS Notification Authorization

Request notification permissions from the user and retrieve the APNS device token.

JavaScript
import messaging from '@react-native-firebase/messaging';
import { Platform } from 'react-native';

async function requestIOSNotificationPermission() {
  if (Platform.OS !== 'ios') {
    return false;
  }

  try {
    const authStatus = await messaging().requestPermission();
    const enabled =
      authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
      authStatus === messaging.AuthorizationStatus.PROVISIONAL;

    if (enabled) {
      console.log('iOS notification authorization status:', authStatus);
      return true;
    }

    return false;
  } catch (error) {
    console.error('Error requesting iOS notification permission:', error);
    return false;
  }
}

Step 2: Retrieve and Register APNS Token

Get the APNS device token and register it with Singular using setUninstallToken() after authorization is granted.

New ArchitectureOld Achitecture
// TurboModule direct API (React Native 0.76+ New Architecture)
import React, { useEffect } from 'react';
import NativeSingular from 'singular-react-native/js/NativeSingular';
import messaging from '@react-native-firebase/messaging';
import { Platform } from 'react-native';

export default function App() {
  useEffect(() => {
    if (Platform.OS === 'ios') {
      initializeIOSUninstallTracking();
    }
  }, []);

  async function initializeIOSUninstallTracking() {
    try {
      // Request notification authorization
      const hasPermission = await requestIOSNotificationPermission();

      if (!hasPermission) {
        console.warn('Notification permission denied - uninstall tracking unavailable');
        return;
      }

      // Register for remote notifications (required for APNS)
      await messaging().registerDeviceForRemoteMessages();

      // Get APNS token
      const apnsToken = await messaging().getAPNSToken();

      if (apnsToken) {
        // Register token with Singular for uninstall tracking
        NativeSingular.setUninstallToken(apnsToken);
        console.log('APNS token registered with Singular:', apnsToken);
      } else {
        console.warn('No APNS token available');
      }
    } catch (error) {
      console.error('Error setting up iOS uninstall tracking:', error);
    }
  }

  async function requestIOSNotificationPermission() {
    // Implementation from Step 1 above
    const authStatus = await messaging().requestPermission();
    return authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
           authStatus === messaging.AuthorizationStatus.PROVISIONAL;
  }

  return (
    // Your app components
    null
  );
}

Token Format: The APNS token retrieved from getAPNSToken() is already formatted as a hexadecimal string, which is the correct format for Singular.


Step 3: Handle Token Refresh (iOS)

Update the APNS token with Singular if it changes during the app lifecycle.

New ArchitectureOld Achitecture
// TurboModule direct API (React Native 0.76+ New Architecture)
import React, { useEffect } from 'react';
import NativeSingular from 'singular-react-native/js/NativeSingular';
import messaging from '@react-native-firebase/messaging';
import { Platform } from 'react-native';

export default function App() {
  useEffect(() => {
    if (Platform.OS === 'ios') {
      // Set up token refresh listener
      const unsubscribe = messaging().onTokenRefresh((token) => {
        console.log('APNS token refreshed:', token);

        // Update Singular with new token
        NativeSingular.setUninstallToken(token);
      });

      // Clean up listener on unmount
      return () => unsubscribe();
    }
  }, []);

  return (
    // Your app components
    null
  );
}

Complete Cross-Platform Implementation

Unified Uninstall Tracking Setup

Implement uninstall tracking for both Android and iOS platforms with proper error handling and token refresh logic.

New ArchitectureOld Achitecture
// TurboModule direct API (React Native 0.76+ New Architecture)
import React, { useEffect } from 'react';
import NativeSingular from 'singular-react-native/js/NativeSingular';
import messaging from '@react-native-firebase/messaging';
import { Platform, PermissionsAndroid } from 'react-native';

export default function App() {
  useEffect(() => {
    initializeUninstallTracking();
    const unsubscribe = setupTokenRefreshListener();

    // Clean up listener on unmount
    return () => {
      if (typeof unsubscribe === 'function') unsubscribe();
    };
  }, []);

  async function initializeUninstallTracking() {
    try {
      if (Platform.OS === 'android') {
        await setupAndroidUninstallTracking();
      } else if (Platform.OS === 'ios') {
        await setupIOSUninstallTracking();
      }
    } catch (error) {
      console.error('Error initializing uninstall tracking:', error);
    }
  }

  async function setupAndroidUninstallTracking() {
    // Request permission for Android 13+
    if (Platform.Version >= 33) {
      const granted = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
      );

      if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
        console.warn('Android notification permission denied');
        return;
      }
    }

    // Get and register FCM token
    const token = await messaging().getToken();

    if (token) {
      NativeSingular.setUninstallToken(token);
      console.log('Android FCM token registered:', token);
    } else {
      console.warn('Failed to retrieve Android FCM token');
    }
  }

  async function setupIOSUninstallTracking() {
    // Request iOS notification authorization
    const authStatus = await messaging().requestPermission();
    const authorized =
      authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
      authStatus === messaging.AuthorizationStatus.PROVISIONAL;

    if (!authorized) {
      console.warn('iOS notification permission denied');
      return;
    }

    // Register for remote notifications
    await messaging().registerDeviceForRemoteMessages();

    // Get and register APNS token
    const apnsToken = await messaging().getAPNSToken();

    if (apnsToken) {
      NativeSingular.setUninstallToken(apnsToken);
      console.log('iOS APNS token registered:', apnsToken);
    } else {
      console.warn('Failed to retrieve iOS APNS token');
    }
  }

  function setupTokenRefreshListener() {
    // Listen for token refresh events
    const unsubscribe = messaging().onTokenRefresh((token) => {
      console.log(`${Platform.OS.toUpperCase()} token refreshed:`, token);
      NativeSingular.setUninstallToken(token);
    });

    return unsubscribe;
  }

  return (
    // Your app components
    null
  );
}

Verification and Troubleshooting

Verify Implementation

Confirm uninstall tracking is working correctly before deploying to production.

  1. Check Logs: Verify token registration appears in your console logs with the correct format
  2. Test Token Generation: Ensure tokens are generated on first app launch after granting permissions
  3. Monitor Dashboard: Check Singular dashboard for uninstall tracking data after 24-48 hours
  4. Test Token Refresh: Clear app data and verify token updates correctly when app relaunches

Common Issues

  • Token Not Generated: Verify Firebase dependencies are correctly installed and Firebase is configured in your React Native project
  • Permission Denied: Check that users have granted notification permissions (required for both Android 13+ and all iOS versions)
  • Token Not Updating: Ensure you've subscribed to the onTokenRefresh event for both platforms
  • Missing Data: Confirm devices meet platform requirements (Android 4.1+ with Google Play Services, iOS with APNs support)
  • Configuration Error: Verify uninstall tracking is enabled in Singular platform settings for your app
  • Firebase Setup: For Android, ensure google-services.json is in android/app/. For iOS, ensure GoogleService-Info.plist is added to Xcode project