npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gabriel-sisjr/react-native-background-location

v0.8.2

Published

React Native library for background location tracking using TurboModules. Track user location even when the app is minimized or in the background.

Readme

@gabriel-sisjr/react-native-background-location

NPM Version NPM Beta NPM Downloads NPM Total Downloads CI Tests Code Coverage Pre-release CI Release CI GitHub Stars License Bundlephobia Platform TypeScript

A React Native library for tracking location in the background using TurboModules (New Architecture). Track user location even when the app is minimized or in the background.

Tracking demo

Features

  • Background location tracking - Continues tracking when app is in background
  • Real-time location updates - Automatic event-driven location watching
  • Crash recovery - Automatic recovery of tracking sessions after app crash or restart
  • Battery optimization - Configurable accuracy levels and update intervals for efficient battery usage
  • TurboModule - Built with React Native's New Architecture for better performance
  • Session-based tracking - Organize location data by trip/session IDs
  • TypeScript support - Fully typed API
  • Android support - Native Kotlin implementation (iOS coming soon)
  • Persistent storage - Locations are stored in Room Database and survive app restarts
  • Foreground service - Uses Android foreground service for reliable tracking

Installation

npm install @gabriel-sisjr/react-native-background-location
# or
yarn add @gabriel-sisjr/react-native-background-location

Platform Configuration

Android

1. Add Permissions to AndroidManifest.xml

Edit android/app/src/main/AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

  <!-- Location permissions -->
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

  <!-- Background location for Android 10+ -->
  <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

  <!-- Foreground service permissions -->
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />

  <application>
    <!-- Your app configuration -->
  </application>
</manifest>

2. Request Permissions (Critical for Android 11+)

⚠️ IMPORTANT: On Android 11 (API 30) and higher, you MUST request background location permission separately from foreground permissions. Requesting them together will silently fail.

import { PermissionsAndroid, Platform, Alert } from 'react-native';

async function requestLocationPermissions(): Promise<boolean> {
  if (Platform.OS !== 'android') {
    return true;
  }

  try {
    // Step 1: Request foreground permissions FIRST
    const foregroundPermissions = await PermissionsAndroid.requestMultiple([
      PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
      PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
    ]);

    const foregroundGranted =
      foregroundPermissions['android.permission.ACCESS_FINE_LOCATION'] === 'granted' ||
      foregroundPermissions['android.permission.ACCESS_COARSE_LOCATION'] === 'granted';

    if (!foregroundGranted) {
      return false;
    }

    // Step 2: For Android 10+, request background permission SEPARATELY
    // On Android 11+, this MUST be a separate request with user education
    if (Platform.Version >= 29) {
      // Show rationale before requesting (required for good UX and Play Store compliance)
      await new Promise<void>((resolve) => {
        Alert.alert(
          'Background Location Required',
          'To track your location when the app is in the background, please select "Allow all the time" on the next screen.',
          [{ text: 'Continue', onPress: () => resolve() }]
        );
      });

      const backgroundPermission = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION
      );

      return backgroundPermission === 'granted';
    }

    return true;
  } catch (error) {
    console.error('Permission request failed:', error);
    return false;
  }
}

Why this matters:

  • Android 11+ requires a two-step permission flow
  • The system shows different UI for background location
  • Requesting all permissions together silently fails for background
  • Users must explicitly choose "Allow all the time" in system settings

iOS

iOS support is not yet available. For cross-platform apps, consider:

The iOS implementation (when available) will maintain API compatibility with the current TypeScript interface.

Usage

Using React Hooks (Recommended)

The easiest way to use the library is with React Hooks:

import {
  useLocationPermissions,
  useBackgroundLocation,
  useLocationUpdates,
  LocationAccuracy,
  NotificationPriority,
  type TrackingOptions,
} from '@gabriel-sisjr/react-native-background-location';

function TrackingScreen() {
  // Manage permissions
  const { permissionStatus, requestPermissions } = useLocationPermissions();

  // Configure tracking options
  const trackingOptions: TrackingOptions = {
    updateInterval: 5000, // 5 seconds
    fastestInterval: 3000, // 3 seconds
    maxWaitTime: 10000, // 10 seconds
    accuracy: LocationAccuracy.HIGH_ACCURACY,
    waitForAccurateLocation: false,
    notificationTitle: 'Location Tracking',
    notificationText: 'Tracking your location in background',
    notificationPriority: NotificationPriority.LOW,
  };

  // Manage tracking (for start/stop control)
  const {
    startTracking,
    stopTracking,
    isTracking,
  } = useBackgroundLocation({
    onError: (err) => console.error(err),
  });

  // Watch real-time location updates
  const {
    locations,
    lastLocation,
  } = useLocationUpdates({
    onLocationUpdate: (location) => {
      console.log('New location:', location);
    },
  });

  // Request permissions first
  if (!permissionStatus.hasPermission) {
    return <Button title="Grant Permissions" onPress={requestPermissions} />;
  }

  const handleStartTracking = () => {
    startTracking(undefined, trackingOptions);
  };

  return (
    <View>
      <Text>Status: {isTracking ? 'Tracking' : 'Stopped'}</Text>
      <Text>Locations: {locations.length}</Text>
      {lastLocation && (
        <>
          <Text>Last: {lastLocation.latitude}, {lastLocation.longitude}</Text>
          {lastLocation.accuracy !== undefined && (
            <Text>Accuracy: {lastLocation.accuracy.toFixed(2)} m</Text>
          )}
          {lastLocation.speed !== undefined && (
            <Text>Speed: {(lastLocation.speed * 3.6).toFixed(2)} km/h</Text>
          )}
        </>
      )}
      <Button
        title={isTracking ? 'Stop' : 'Start'}
        onPress={isTracking ? stopTracking : handleStartTracking}
      />
    </View>
  );
}

Real-Time Updates Hook

The useLocationUpdates hook provides automatic, real-time location updates:

import { useLocationUpdates } from '@gabriel-sisjr/react-native-background-location';

function LiveTrackingScreen() {
  const {
    locations,
    lastLocation,
    isTracking,
    tripId,
    error,
  } = useLocationUpdates({
    onLocationUpdate: (location) => {
      console.log('New location received:', location);
    },
  });

  return (
    <View>
      <Text>Locations: {locations.length}</Text>
      {lastLocation && (
        <>
          <Text>Last: {lastLocation.latitude}, {lastLocation.longitude}</Text>
          {lastLocation.accuracy !== undefined && (
            <Text>Accuracy: {lastLocation.accuracy.toFixed(2)} m</Text>
          )}
          {lastLocation.speed !== undefined && (
            <Text>Speed: {(lastLocation.speed * 3.6).toFixed(2)} km/h</Text>
          )}
        </>
      )}
    </View>
  );
}

Callback Throttling (v0.8.0+)

The onUpdateInterval option allows you to throttle callback execution while still collecting all location updates:

// Throttle callback for server sync (locations still collected at updateInterval)
useLocationUpdates({
  onLocationUpdate: (location) => {
    // This callback is called at most every 30 seconds
    syncToServer(location);
  },
  onUpdateInterval: 30000, // Sync every 30 seconds max
});

// Combine with distance filter for maximum efficiency
const trackingOptions: TrackingOptions = {
  distanceFilter: 50, // Only update if moved 50+ meters
  updateInterval: 5000, // Check every 5 seconds
};

await BackgroundLocation.startTracking(trackingOptions);

useLocationUpdates({
  onLocationUpdate: (location) => {
    // Called when device moves 50+ meters AND at most every 60 seconds
    uploadToServer(location);
  },
  onUpdateInterval: 60000, // Upload at most every 60 seconds
});

Key differences:

  • distanceFilter (TrackingOptions): Filters location collection at the native level
  • onUpdateInterval (useLocationUpdates): Throttles callback execution but locations are still collected
  • Combine both for optimal battery life and network efficiency

See the Hooks Guide for complete hook documentation. See the Real-Time Updates Guide for real-time location watching.

Using Direct API

You can also use the module API directly:

import BackgroundLocation, {
  type Coords,
  LocationAccuracy,
  NotificationPriority,
  type TrackingOptions,
} from '@gabriel-sisjr/react-native-background-location';

// Configure tracking options
const options: TrackingOptions = {
  updateInterval: 5000,
  fastestInterval: 3000,
  maxWaitTime: 10000,
  accuracy: LocationAccuracy.HIGH_ACCURACY,
  waitForAccurateLocation: false,
  notificationTitle: 'Location Tracking',
  notificationText: 'Tracking your location in background',
  notificationPriority: NotificationPriority.LOW,
};

// Recommended: Let the library generate a unique trip ID
const tripId = await BackgroundLocation.startTracking(undefined, options);

// Optional: Resume tracking with an existing trip ID (for crash recovery)
// Only use this to resume a previously interrupted tracking session
const resumedTripId =
  await BackgroundLocation.startTracking('existing-trip-123', options);

// Check if tracking is active
const status = await BackgroundLocation.isTracking();
console.log(status.active); // true/false
console.log(status.tripId); // current trip ID if active

// Get all locations for a trip
const locations: Coords[] = await BackgroundLocation.getLocations(tripId);
locations.forEach((location) => {
  console.log(location.latitude); // string
  console.log(location.longitude); // string
  console.log(location.timestamp); // number (Unix timestamp in ms)
  
  // Extended properties (optional, check for undefined)
  if (location.accuracy !== undefined) {
    console.log(`Accuracy: ${location.accuracy} meters`);
  }
  if (location.speed !== undefined) {
    console.log(`Speed: ${location.speed} m/s`);
  }
  if (location.altitude !== undefined) {
    console.log(`Altitude: ${location.altitude} meters`);
  }
  // ... and more properties available
});

// Stop tracking
await BackgroundLocation.stopTracking();

// Clear stored data for a trip
await BackgroundLocation.clearTrip(tripId);

API Reference

startTracking()

Starts location tracking in background for a new or existing trip.

Overload 1: Options only (tripId auto-generated)

startTracking(options?: TrackingOptions): Promise<string>

Overload 2: With tripId

startTracking(tripId?: string, options?: TrackingOptions): Promise<string>
  • Parameters:

    • tripId (optional): Existing trip identifier to resume tracking. If omitted, a new UUID will be generated.

      ⚠️ Important: Only provide a tripId when resuming an interrupted tracking session (e.g., after app crash, battery drain, etc.). For new trips, always omit this parameter to let the library generate a unique UUID. This prevents data overwriting and ensures each trip has a unique identifier.

    • options (optional): Configuration options for location tracking. See TrackingOptions for details.

  • Returns: Promise resolving to the effective trip ID being used.

  • Behavior:

    • If tracking is already active, returns the current trip ID (idempotent).
    • Starts a foreground service on Android with a persistent notification.
    • Requires location permissions to be granted.
    • New trips: Generates a unique UUID to prevent collisions.
    • Resuming trips: Continues collecting locations to the existing trip data.
    • Uses provided options or defaults if not specified.
  • Best Practice:

    // ✅ Good: Start a new trip with default options
    const newTripId = await startTracking();
    
    // ✅ Good: Start a new trip with custom options
    const customTripId = await startTracking(undefined, {
      accuracy: LocationAccuracy.HIGH_ACCURACY,
      updateInterval: 2000,
    });
    
    // ✅ Good: Resume after interruption
    const resumedTripId = await startTracking(previousTripId);
    
    // ❌ Avoid: Don't create new trips with custom IDs
    const badTripId = await startTracking('my-custom-id');
  • Throws:

    • PERMISSION_DENIED if location permissions are not granted.
    • START_TRACKING_ERROR if unable to start the service.

stopTracking(): Promise<void>

Stops all location tracking and terminates the background service.

  • Returns: Promise that resolves when tracking is stopped.

  • Behavior:

    • Removes the foreground service and notification.
    • Does not clear stored location data (use clearTrip() for that).

isTracking(): Promise<TrackingStatus>

Checks if location tracking is currently active.

  • Returns: Promise resolving to an object:
    {
      active: boolean;      // Whether tracking is active
      tripId?: string;      // Current trip ID if tracking
    }

getLocations(tripId: string): Promise<Coords[]>

Retrieves all stored location points for a specific trip.

  • Parameters:

    • tripId: The trip identifier.
  • Returns: Promise resolving to array of location coordinates with extended properties:

    {
      latitude: string; // Latitude as string
      longitude: string; // Longitude as string
      timestamp: number; // Unix timestamp in milliseconds
      // Extended properties (optional, available when provided by location provider)
      accuracy?: number; // Horizontal accuracy in meters
      altitude?: number; // Altitude in meters above sea level
      speed?: number; // Speed in meters per second
      bearing?: number; // Bearing in degrees (0-360)
      verticalAccuracyMeters?: number; // Vertical accuracy (Android API 26+)
      speedAccuracyMetersPerSecond?: number; // Speed accuracy (Android API 26+)
      bearingAccuracyDegrees?: number; // Bearing accuracy (Android API 26+)
      elapsedRealtimeNanos?: number; // Elapsed realtime in nanoseconds
      provider?: string; // Location provider (gps, network, passive, etc.)
      isFromMockProvider?: boolean; // Whether from mock provider (Android API 18+)
    }[];
  • Throws:

    • INVALID_TRIP_ID if trip ID is empty.
    • GET_LOCATIONS_ERROR if unable to retrieve data.

clearTrip(tripId: string): Promise<void>

Clears all stored location data for a specific trip.

  • Parameters:

    • tripId: The trip identifier to clear.
  • Returns: Promise that resolves when data is cleared.

  • Throws:

    • INVALID_TRIP_ID if trip ID is empty.
    • CLEAR_TRIP_ERROR if unable to clear data.

Types

interface Coords {
  latitude: string; // Latitude in decimal degrees
  longitude: string; // Longitude in decimal degrees
  timestamp: number; // Timestamp in milliseconds since Unix epoch
  
  // Extended location properties (optional, available when provided by location provider)
  accuracy?: number; // Horizontal accuracy in meters
  altitude?: number; // Altitude in meters above sea level
  speed?: number; // Speed in meters per second
  bearing?: number; // Bearing in degrees (0-360)
  verticalAccuracyMeters?: number; // Vertical accuracy in meters (Android API 26+)
  speedAccuracyMetersPerSecond?: number; // Speed accuracy in meters per second (Android API 26+)
  bearingAccuracyDegrees?: number; // Bearing accuracy in degrees (Android API 26+)
  elapsedRealtimeNanos?: number; // Elapsed realtime in nanoseconds since system boot
  provider?: string; // Location provider (gps, network, passive, etc.)
  isFromMockProvider?: boolean; // Whether the location is from a mock provider (Android API 18+)
}

interface TrackingStatus {
  active: boolean;
  tripId?: string;
}

interface TrackingOptions {
  updateInterval?: number; // Interval between location updates in milliseconds (default: 5000)
  fastestInterval?: number; // Fastest interval between location updates in milliseconds (default: 3000)
  maxWaitTime?: number; // Maximum wait time in milliseconds before delivering location updates (default: 10000)
  accuracy?: LocationAccuracy; // Location accuracy priority (default: LocationAccuracy.HIGH_ACCURACY)
  waitForAccurateLocation?: boolean; // Whether to wait for accurate location before delivering updates (default: false)
  distanceFilter?: number; // Minimum distance in meters between location updates (default: 0) - Android only
  onUpdateInterval?: number; // Throttle callback execution in milliseconds - locations still collected but callbacks limited (default: undefined)
  notificationTitle?: string; // Notification title for foreground service (default: "Location Tracking")
  notificationText?: string; // Notification text for foreground service (default: "Tracking your location in background")
  notificationChannelName?: string; // Notification channel name (Android) (default: "Background Location")
  notificationPriority?: NotificationPriority; // Notification priority (Android) (default: NotificationPriority.LOW)
  foregroundOnly?: boolean; // Track only while app is visible (default: false) - does not require background permission
}

Enums

LocationAccuracy

Location accuracy priority levels:

enum LocationAccuracy {
  HIGH_ACCURACY = 'HIGH_ACCURACY', // Highest accuracy - uses GPS and other sensors (default)
  BALANCED_POWER_ACCURACY = 'BALANCED_POWER_ACCURACY', // Balanced accuracy and power consumption
  LOW_POWER = 'LOW_POWER', // Low power consumption - uses network-based location
  NO_POWER = 'NO_POWER', // No power consumption - only receives location updates when other apps request them
  PASSIVE = 'PASSIVE', // Passive location updates - receives location updates from other apps
}

NotificationPriority

Notification priority levels for Android:

enum NotificationPriority {
  LOW = 'LOW', // Low priority - minimal notification (default)
  DEFAULT = 'DEFAULT', // Default priority
  HIGH = 'HIGH', // High priority - more prominent notification
  MAX = 'MAX', // Maximum priority - urgent notification
}

LocationPermissionStatus

Location permission status:

enum LocationPermissionStatus {
  GRANTED = 'granted',
  DENIED = 'denied',
  BLOCKED = 'blocked',
  UNDETERMINED = 'undetermined',
}

Configuration

The library provides configurable tracking options through TrackingOptions. You can customize location update intervals, accuracy, and notification settings.

Default Configuration

The library uses the following default settings:

  • Update interval: 5 seconds (5000ms)
  • Fastest interval: 3 seconds (3000ms)
  • Max wait time: 10 seconds (10000ms)
  • Accuracy: LocationAccuracy.HIGH_ACCURACY
  • Wait for accurate location: false
  • Notification title: "Location Tracking"
  • Notification text: "Tracking your location in background"
  • Notification channel name: "Background Location"
  • Notification priority: NotificationPriority.LOW

Customizing Configuration

You can customize tracking options when starting tracking:

import {
  BackgroundLocation,
  LocationAccuracy,
  NotificationPriority,
  type TrackingOptions,
} from '@gabriel-sisjr/react-native-background-location';

// High accuracy preset (for navigation)
const highAccuracyOptions: TrackingOptions = {
  updateInterval: 2000,
  fastestInterval: 1000,
  maxWaitTime: 5000,
  accuracy: LocationAccuracy.HIGH_ACCURACY,
  waitForAccurateLocation: true,
  notificationTitle: 'High Accuracy Tracking',
  notificationText: 'Using GPS for precise location tracking',
  notificationPriority: NotificationPriority.DEFAULT,
};

// Balanced preset (for most tracking use cases)
const balancedOptions: TrackingOptions = {
  updateInterval: 10000,
  fastestInterval: 5000,
  maxWaitTime: 15000,
  accuracy: LocationAccuracy.BALANCED_POWER_ACCURACY,
  waitForAccurateLocation: false,
  notificationPriority: NotificationPriority.LOW,
};

// Low power preset (for battery efficiency)
const lowPowerOptions: TrackingOptions = {
  updateInterval: 30000,
  fastestInterval: 15000,
  maxWaitTime: 60000,
  accuracy: LocationAccuracy.LOW_POWER,
  waitForAccurateLocation: false,
  notificationTitle: 'Low Power Tracking',
  notificationText: 'Power-efficient location tracking',
  notificationPriority: NotificationPriority.LOW,
};

// Distance filter for battery efficiency (v0.8.0+)
const distanceFilterOptions: TrackingOptions = {
  distanceFilter: 50, // Only update if moved 50+ meters
  accuracy: LocationAccuracy.HIGH_ACCURACY,
  updateInterval: 5000,
  notificationTitle: 'Efficient Tracking',
  notificationText: 'Tracking significant location changes',
};

// Start tracking with custom options
const tripId = await BackgroundLocation.startTracking(undefined, highAccuracyOptions);

// Start tracking with distance filter (new overload)
const filteredTripId = await BackgroundLocation.startTracking(distanceFilterOptions);

Configuration Presets

The example app includes predefined configuration presets that you can use as a reference:

  • High Accuracy: Optimized for navigation and precise tracking (2s interval, GPS)
  • Balanced: Good balance between accuracy and battery (10s interval, balanced accuracy)
  • Low Power: Optimized for battery efficiency (30s interval, network-based)

See the example app for complete implementation examples.

Foreground-Only Mode

For apps that don't need background tracking or when background permission is denied, you can use foreground-only mode:

const tripId = await BackgroundLocation.startTracking(undefined, {
  foregroundOnly: true,
  // Other options...
});

When to use:

  • Privacy-focused apps where users prefer not to grant background permission
  • Development and testing without background permission setup
  • Fallback when background permission is denied
  • Apps that only need tracking while actively used

Behavior:

  • Tracking only works while the app is in foreground or visible
  • Does not require ACCESS_BACKGROUND_LOCATION permission
  • Foreground service still runs (with notification) for reliability
  • Tracking pauses when app goes to background

Crash Recovery & Session Persistence

The library automatically persists tracking state and location data. Here's how to handle app restarts and crashes:

How It Works

  1. Tracking state is stored in SharedPreferences and survives app restarts
  2. Location data is stored in Room database and persists across sessions
  3. Foreground service continues running briefly after app crash (until system kills it)

Resuming After App Restart

Always check for active sessions when your app starts:

import { useEffect, useState } from 'react';
import BackgroundLocation from '@gabriel-sisjr/react-native-background-location';

function App() {
  const [tripId, setTripId] = useState<string | null>(null);

  useEffect(() => {
    const checkActiveSession = async () => {
      const status = await BackgroundLocation.isTracking();

      if (status.active && status.tripId) {
        // Active session found - the service is still running
        console.log('Resuming active session:', status.tripId);
        setTripId(status.tripId);
      } else if (status.tripId && !status.active) {
        // Trip ID exists but service stopped (crash/kill scenario)
        // Option 1: Resume tracking
        const resumedId = await BackgroundLocation.startTracking(status.tripId);
        setTripId(resumedId);

        // Option 2: Just recover the data
        const locations = await BackgroundLocation.getLocations(status.tripId);
        console.log('Recovered locations:', locations.length);
      }
    };

    checkActiveSession();
  }, []);

  // ... rest of your app
}

Best Practices

  1. Always check on startup: Call isTracking() when your app initializes
  2. Persist tripId externally: Store the tripId in your app's state management or AsyncStorage
  3. Handle orphaned data: Decide whether to resume tracking or just recover locations
  4. Clean up old trips: Call clearTrip() after uploading data to your server

Coordinate Format

Note: Coordinates (latitude, longitude) are returned as strings, not numbers.

This design choice preserves maximum precision without floating-point rounding issues. Always parse when using with map libraries:

// ✅ Correct: Parse coordinates for map libraries
<Marker
  coordinate={{
    latitude: parseFloat(location.latitude),
    longitude: parseFloat(location.longitude),
  }}
/>

// ✅ Helper function for convenience
const toNumericCoords = (loc: Coords) => ({
  latitude: parseFloat(loc.latitude),
  longitude: parseFloat(loc.longitude),
});

// Usage with react-native-maps
<Polyline coordinates={locations.map(toNumericCoords)} />

// ❌ Wrong: Using strings directly will cause errors
<Marker coordinate={{ latitude: location.latitude, longitude: location.longitude }} />

Memory Management

The locations array grows with each collected point. For long tracking sessions, implement these strategies:

Memory Considerations

  • Typical memory usage: ~500 bytes per location point
  • 1-hour trip at 5s intervals: ~720 points ≈ 360KB
  • 8-hour trip at 5s intervals: ~5,760 points ≈ 2.8MB

Strategies for Long Trips

// Strategy 1: Upload and clear periodically
const UPLOAD_THRESHOLD = 100; // Upload every 100 points

useLocationUpdates({
  onLocationUpdate: async (location) => {
    const locations = await BackgroundLocation.getLocations(tripId);

    if (locations.length >= UPLOAD_THRESHOLD) {
      await uploadToServer(tripId, locations);
      await BackgroundLocation.clearTrip(tripId);
      // Tracking continues with fresh storage
    }
  },
});

// Strategy 2: Display only recent locations
function LocationList({ locations }: { locations: Coords[] }) {
  const recentLocations = locations.slice(-50); // Show last 50 only

  return (
    <FlatList
      data={recentLocations}
      renderItem={({ item }) => <LocationItem location={item} />}
    />
  );
}

// Strategy 3: Virtualized list for all locations
import { FlashList } from '@shopify/flash-list';

function AllLocations({ locations }: { locations: Coords[] }) {
  return (
    <FlashList
      data={locations}
      renderItem={({ item }) => <LocationItem location={item} />}
      estimatedItemSize={80}
    />
  );
}

Battery Optimization

The library includes built-in battery optimization features:

  • Configurable accuracy levels - Use LocationAccuracy.LOW_POWER or BALANCED_POWER_ACCURACY for better battery efficiency
  • Adjustable update intervals - Increase intervals to reduce battery consumption
  • Smart location updates - Only requests location when necessary
  • Foreground service optimization - Efficient service implementation

Best Practices

  • Use LocationAccuracy.LOW_POWER for long-term tracking
  • Increase updateInterval when high-frequency updates aren't needed
  • Stop tracking when not in use
  • Inform users about battery usage
  • Test on real devices (emulator GPS simulation is unreliable)

Android Manufacturer-Specific Battery Optimization

Many Android manufacturers implement aggressive battery optimization that can kill background services. This is not a library bug but a platform behavior.

Affected manufacturers:

  • Xiaomi (MIUI): Auto-start permissions, battery saver
  • Huawei (EMUI/HarmonyOS): App launch management, power-intensive prompt
  • Samsung (OneUI): Sleeping apps, deep sleeping apps
  • Oppo (ColorOS): Battery optimization, auto-start
  • Vivo (FuntouchOS): Background app management
  • OnePlus (OxygenOS): Battery optimization, deep optimization
  • Realme (Realme UI): App battery management

Recommended approach:

import { Linking, Alert, Platform } from 'react-native';

function promptBatteryOptimization() {
  if (Platform.OS !== 'android') return;

  Alert.alert(
    'Keep Tracking Active',
    'To ensure reliable location tracking, please disable battery optimization for this app.\n\n' +
    '1. Tap "Open Settings"\n' +
    '2. Find this app\n' +
    '3. Select "Don\'t optimize" or "No restrictions"',
    [
      { text: 'Later', style: 'cancel' },
      {
        text: 'Open Settings',
        onPress: () => {
          // Opens battery optimization settings (may vary by manufacturer)
          Linking.openSettings();
        },
      },
    ]
  );
}

// Show this prompt after starting tracking for the first time
// or when users report tracking issues

Manufacturer-specific settings paths:

  • Xiaomi: Settings → Apps → Manage apps → [App] → Autostart + Battery saver
  • Huawei: Settings → Battery → App launch → [App] → Manage manually
  • Samsung: Settings → Battery → Background usage limits → Never sleeping apps
  • Oppo/Realme: Settings → Battery → [App] → Allow background activity

For a comprehensive database of manufacturer-specific instructions, see dontkillmyapp.com.

Google Play Store Compliance

Apps using background location must comply with Google Play's policies. Non-compliance will result in app rejection or removal.

Required Steps

1. Privacy Policy

Your privacy policy must explicitly mention:

  • That you collect location data
  • Whether it's collected in the background
  • How the data is used
  • How long it's retained
  • How users can request deletion

2. In-App Disclosure (Required Before Permission Request)

Google requires a prominent disclosure before requesting background location:

async function showLocationDisclosure(): Promise<boolean> {
  return new Promise((resolve) => {
    Alert.alert(
      'Location Access',
      'This app collects location data to track your trips even when the app is closed or not in use.\n\n' +
      'This data is used to:\n' +
      '• Record your travel routes\n' +
      '• Calculate trip distances\n' +
      '• [Your specific use case]\n\n' +
      'You can stop tracking at any time from the app.',
      [
        { text: 'Deny', onPress: () => resolve(false), style: 'cancel' },
        { text: 'Accept', onPress: () => resolve(true) },
      ]
    );
  });
}

// Use BEFORE requesting permissions
const userAccepted = await showLocationDisclosure();
if (userAccepted) {
  await requestLocationPermissions();
}

3. Play Console Declarations

In the Google Play Console, you must:

  1. Data Safety Form: Declare location data collection

    • Go to: App content → Data safety
    • Declare: Location data is collected
    • Specify: Collected in background
  2. Permissions Declaration Form:

    • Go to: App content → Sensitive app permissions
    • Fill out the "Background location access" form
    • Provide a video demonstrating the core feature
    • Explain why background access is essential

4. Manifest Metadata (Recommended)

Add metadata explaining background location usage:

<manifest>
  <application>
    <!-- Explain background location usage for reviewers -->
    <meta-data
      android:name="com.google.android.gms.permission.AD_ID"
      android:value="false" />
  </application>
</manifest>

Common Rejection Reasons

| Reason | Solution | |--------|----------| | No in-app disclosure | Add prominent disclosure dialog before permission request | | Disclosure not prominent | Make it a blocking modal, not a toast or small text | | Missing privacy policy | Add privacy policy link in app and Play listing | | Video doesn't show feature | Record video of actual background tracking in use | | Background not essential | Justify why foreground-only won't work |

Foreground-Only Alternative

If background location isn't essential, consider using foregroundOnly: true to avoid the stricter review process:

await BackgroundLocation.startTracking(undefined, {
  foregroundOnly: true,
});

Simulator/Emulator Support

When the native module is not available (e.g., running in simulator without proper setup), all methods will:

  • Log a warning to the console
  • Return safe fallback values
  • Not crash the app

This allows development without constant native setup.

React Hooks

The library provides four React Hooks for easier integration:

useLocationPermissions()

Manages location permissions including background permissions.

const {
  permissionStatus, // Current permission state
  requestPermissions, // Request all permissions
  checkPermissions, // Check without requesting
  isRequesting, // Loading state
} = useLocationPermissions();

useBackgroundLocation(options?)

Complete hook for managing tracking, locations, and state.

const {
  isTracking, // Whether tracking is active
  tripId, // Current trip ID
  locations, // Array of locations
  isLoading, // Loading state
  error, // Last error
  startTracking, // Start tracking
  stopTracking, // Stop tracking
  refreshLocations, // Refresh locations
  clearCurrentTrip, // Clear trip data
  clearError, // Clear error
} = useBackgroundLocation({
  autoStart: false, // Auto-start on mount
  onTrackingStart: (id) => {}, // Callback
  onTrackingStop: () => {}, // Callback
  onError: (err) => {}, // Callback
});

useLocationTracking(autoRefresh?)

Lightweight hook for monitoring tracking status.

const {
  isTracking, // Whether tracking is active
  tripId, // Current trip ID
  refresh, // Refresh status
  isLoading, // Loading state
} = useLocationTracking(true);

useLocationUpdates(options?)

Real-time location updates with automatic event-driven updates.

const {
  locations,            // Array of locations (updates automatically)
  lastLocation,         // Most recent location
  lastWarning,          // Last warning event (SERVICE_TIMEOUT, TASK_REMOVED, etc.)
  isTracking,           // Tracking status
  tripId,               // Current trip ID
  isLoading,            // Loading state
  error,                // Error state
  clearError,           // Clear error
  clearLocations,       // Clear all locations for current trip
} = useLocationUpdates({
  tripId?: string,                                    // Filter by tripId
  onLocationUpdate?: (location) => void,              // Callback per update
  onLocationWarning?: (warning) => void,              // Callback for warnings
  autoLoad?: boolean,                                 // Auto-load existing data (default: true)
});

// Handle service warnings (important for Android 14+/15+)
useLocationUpdates({
  onLocationWarning: (warning) => {
    switch (warning.type) {
      case 'SERVICE_TIMEOUT':
        // Android 15+ foreground service timeout - service is restarting
        console.log('Service restarting due to timeout');
        break;
      case 'TASK_REMOVED':
        // App was swiped from recents - tracking continues
        console.log('App removed from recents, tracking continues');
        break;
      case 'LOCATION_UNAVAILABLE':
        // GPS signal lost
        Alert.alert('Location Unavailable', 'Please check GPS settings');
        break;
    }
  },
});

See the Hooks Guide for complete documentation and examples.

Documentation

Getting Started

Production Guides

Development

Example App

The library includes a complete example app demonstrating all features:

# Run the example app
cd example
yarn install

# Android
yarn android

# iOS (coming soon)
yarn ios

Troubleshooting

Android: Location not updating in background

  1. Ensure all permissions are granted, including ACCESS_BACKGROUND_LOCATION
  2. Check that the foreground service is running (you should see a notification)
  3. Test on a real device (emulator GPS simulation is unreliable)
  4. Check device battery optimization settings
  5. Verify Google Play Services is installed and up to date

Build errors

  1. Make sure you're using React Native 0.70+
  2. Clean build: cd android && ./gradlew clean
  3. Clear Metro cache: yarn start --reset-cache
  4. Rebuild: yarn android

TypeScript errors

Make sure your tsconfig.json includes:

{
  "compilerOptions": {
    "moduleResolution": "node"
  }
}

Roadmap

  • [ ] iOS implementation with Swift
  • [ ] Geofencing support
  • [x] Distance filtering for GPS coordinates (v0.8.0)
  • [ ] Configurable notification appearance
  • [ ] Web support (Geolocation API)

Contributing

See the contributing guide to learn how to contribute to the repository and the development workflow.

Support & Sponsorship

If you find this library useful, please consider:

  • Star the repository - It helps others discover the project
  • 🐛 Report bugs - Help me improve the library
  • 💻 Contribute code - Pull requests are always welcome
  • Sponsor on GitHub - Support ongoing development and maintenance

Sponsor this project to help me continue building and maintaining open source tools for the React Native community.

See the Sponsor page for more details about sponsorship benefits and how your support helps the project.

License

MIT