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

@santoshpk/react-native-background-location-tracking

v1.0.0

Published

Continuous background & foreground location tracking for React Native (new arch / TurboModule)

Readme

react-native-background-location-tracking

Continuous foreground and background location tracking for React Native, built on the new architecture (TurboModule). A free, open-source alternative to the paid react-native-background-geolocation, with honest docs about the OS limits.

const sub = onLocation((loc) => console.log(loc.latitude, loc.longitude));
await requestPermissions();
await startTracking({ interval: 5000 });

Capability matrix

Honesty first — this is what v1 actually does:

| Scenario | Android | iOS | Status | |----------|---------|-----|--------| | App foreground | ✅ continuous | ✅ continuous | v1 | | App backgrounded (process alive) | ✅ foreground service | ✅ background location mode | v1 | | App killed / swiped away | ⚠️ stops | ⚠️ stops | out of scope — future | | Device reboot | ⚠️ stops | ⚠️ stops | out of scope — future |

In scope v1: continuous tracking while the app process is alive — foreground and backgrounded, both platforms. Kill-state / reboot recovery (Android START_STICKY/BOOT_COMPLETED, iOS significant-location-change / geofencing) is not shipped and left to the user.

Requirements

| | Minimum | |---|---| | React Native | 0.76+ with the new architecture enabled | | Android | minSdkVersion 24, Google Play services on the device (Fused Location Provider) | | iOS | inherits your RN version's min_ios_version_supported — the podspec pins nothing of its own |

New architecture only. This library is a TurboModule. There is no old-architecture / bridge fallback, and none is planned — if you're on the legacy architecture, upgrade first.

Installation

npm install @santoshpk/react-native-background-location-tracking
# or
yarn add @santoshpk/react-native-background-location-tracking

Then, for iOS:

cd ios && pod install

Rebuild the app after installing — this ships native code, so a Metro reload is not enough:

npx react-native run-android
npx react-native run-ios

Expo

Works with Expo, but not in Expo Go — Expo Go can't load custom native modules. Use a development build:

npx expo prebuild
npx expo run:android
npx expo run:ios

Add the iOS Info.plist keys below via app.json → ios.infoPlist.

Android setup

Nothing to edit. Permissions and the <service> entry are merged from the library manifest automatically:

ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, ACCESS_BACKGROUND_LOCATION,
FOREGROUND_SERVICE, FOREGROUND_SERVICE_LOCATION, POST_NOTIFICATIONS
<service android:foregroundServiceType="location" />

At runtime, requestPermissions() walks the prompts in the order the OS demands:

  1. ACCESS_FINE_LOCATION
  2. POST_NOTIFICATIONS (Android 13+)
  3. ACCESS_BACKGROUND_LOCATION — user must pick "Allow all the time" (Android 10+)

Non-negotiable OS facts:

  • Android 14+ requires the location foreground-service type (already declared here).
  • The persistent notification cannot be hidden — the OS requires it.

iOS setup

Add to Info.plist — all three keys:

<key>NSLocationWhenInUseUsageDescription</key>
<string>Why you need location while the app is open.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Why you need location in the background.</string>
<key>UIBackgroundModes</key>
<array>
  <string>location</string>
</array>

Then enable Signing & Capabilities → Background Modes → Location updates in Xcode (this is what writes UIBackgroundModes if you prefer the GUI).

Background tracking needs the user to grant "Always" authorization. The module sets allowsBackgroundLocationUpdates = true and pausesLocationUpdatesAutomatically = false. Without the location background mode the library emits an onError warning and keeps running foreground-only.

This library never fakes background execution with silent-audio/VoIP tricks — those get you rejected from the App Store.

Quick start

A complete, working component — copy it as-is:

import { useEffect, useState } from 'react';
import { Button, Text, View } from 'react-native';
import {
  requestPermissions,
  startTracking,
  stopTracking,
  onLocation,
  onError,
  type LocationData,
} from '@santoshpk/react-native-background-location-tracking';

export default function Tracker() {
  const [last, setLast] = useState<LocationData | null>(null);
  const [running, setRunning] = useState(false);

  // Subscribe BEFORE starting, so you don't miss the first fixes.
  useEffect(() => {
    const sub = onLocation(setLast);
    const errSub = onError((e) => console.warn(e.message));
    return () => {
      sub.remove(); // unmount = unsubscribe, else it leaks and keeps firing
      errSub.remove();
    };
  }, []);

  const start = async () => {
    const granted = await requestPermissions({
      title: 'Location needed',
      message: 'MyApp records your route.',
      buttonPositive: 'OK',
    });
    if (!granted) return;

    await startTracking({
      interval: 5000,
      accuracy: 'high',
      notificationTitle: 'Tracking active',
      notificationText: 'Recording your location',
    });
    setRunning(true);
  };

  const stop = () => {
    stopTracking(); // stops native GPS, the Android service + notification
    setRunning(false);
  };

  return (
    <View>
      <Text>{last ? `${last.latitude}, ${last.longitude}` : 'no fix yet'}</Text>
      <Button title="Start" onPress={start} disabled={running} />
      <Button title="Stop" onPress={stop} disabled={!running} />
    </View>
  );
}

Cleanup is two independent levels — do both when you're done:

| Call | What it stops | |------|---------------| | sub.remove() | Your JS callback. Native keeps tracking. | | stopTracking() | Native GPS, foreground service, notification. |

Running the example app

The repo ships a working example that exercises every API.

git clone https://github.com/santoshP0/react-native-background-location-tracking.git
cd react-native-background-location-tracking
yarn install

yarn example start          # Metro, leave it running
yarn example android        # in a second terminal
yarn example ios

Then tap Start. Coordinates stream into the list; the interval is editable in the UI.

Faking movement on an emulator/simulator

A stationary emulator has no GPS — feed it positions:

# Android (emulator must be a Google APIs image — Fused needs Play services)
adb emu geo fix 77.5946 12.9716     # note: LONGITUDE first
adb emu geo fix 77.6100 12.9900     # move it

# iOS
xcrun simctl location booted set 12.9716,77.5946    # note: LATITUDE first

Fixes from an emulator arrive with fromMockProvider: true, and speed/course stay 0 unless you use the simulator's route-playback feature.

Verifying background tracking really works

# Android — background the app, then confirm the service is still alive
adb shell input keyevent KEYCODE_HOME
adb shell dumpsys activity services <your.package> | grep isForeground
# expect: isForeground=true ... types=0x00000008   (= FOREGROUND_SERVICE_TYPE_LOCATION)

# watch events keep arriving while backgrounded
adb logcat -v time ReactNativeJS:V '*:S'

On iOS, background the app and watch for the blue status-bar location indicator — events keep flowing to your onLocation callback.

API

| Function | Description | |----------|-------------| | requestPermissions(rationale?) | Request location (+ notification + background on Android). Optional rationale { title, message, buttonPositive } explains why — shown by Android when the user previously declined. Resolves true when foreground location is granted (precise or approximate). iOS no-op — the prompt fires on startTracking, reason text comes from Info.plist. | | startTracking(options?) | Start continuous tracking. Returns Promise<void>. | | stopTracking() | Stop tracking and the foreground service. | | getCurrentLocation() | One-shot fix. Returns Promise<LocationData>; always resolves or rejects, never hangs. | | isTracking() | Returns Promise<boolean>. | | onLocation(cb) | Subscribe to updates. Returns a subscription — call .remove(). | | onError(cb) | Subscribe to warnings/errors. Returns a subscription. |

startTracking options

| Option | Type | Default | Notes | |--------|------|---------|-------| | interval | number | 5000 | ms. Android: native request rate. iOS: heartbeat (see below). | | fastestInterval | number | = interval | ms. Android only — fastest rate your app can handle. | | distanceFilter | number | 0 | Metres of movement between updates. | | accuracy | 'high' \| 'balanced' \| 'low' \| 'passive' | 'high' | Accuracy/battery tradeoff. | | notificationTitle | string | 'Location tracking active' | Android notification title. | | notificationText | string | 'Tracking your location' | Android notification body. | | notificationChannelName | string | 'Location tracking' | Android channel display name. |

LocationData: { latitude, longitude, accuracy, altitude, speed, course, timestamp, fromMockProvider } — timestamp is epoch millis.

How events flow

Native side emits, JS side listens — one-way:

startTracking(options)
  Android: FusedLocationProviderClient fires every `interval` ms
  iOS:     CLLocationManager fires on movement; a heartbeat re-emits the
           latest fix if nothing arrived within `interval` ms
        ↓
  native emits the 'location' event
        ↓
  onLocation(callback) fires with LocationData

Platform nuance: Android's interval is the native request rate. iOS CoreLocation is movement-driven, so this library adds a heartbeat — when the device is still, the latest known position is re-emitted every interval ms with a fresh timestamp (the emission time), matching how Android's Fused provider yields a new fix every interval even while stationary. Real movement updates carry the OS's own fix time. Use distanceFilter to thin out movement updates.

While stationary the coordinates repeat — that's expected. Log the arrival time (new Date()), not loc.timestamp, to see the interval ticking.

Rejection codes

| Code | When | |------|------| | PERMISSION_DENIED | Android: startTracking/getCurrentLocation without location permission. | | NOT_AUTHORIZED | iOS: startTracking while location access is denied/restricted in Settings. | | NO_LOCATION | Android: no fix available for getCurrentLocation. | | LOCATION_ERROR | One-shot fix failed (both platforms). | | MODULE_INVALIDATED | iOS: pending getCurrentLocation when React reloads/shuts down. | | START_FAILED | Android: service failed to start. |

iOS extras you get without any setup: the OS blue location indicator while tracking in background, and on iOS 17+ a CLBackgroundActivitySession that keeps tracking alive while stationary in the background.

Troubleshooting

| Symptom | Cause | Fix | |---------|-------|-----| | TurboModuleRegistry.getEnforcing(...): 'BackgroundLocationTracking' could not be found | Native code not in the binary. | Rebuild (run-android / run-ios); a Metro reload won't do it. On iOS re-run pod install. | | PlatformConstants could not be found | The app is loading a different project's Metro on port 8081. | Stop the other dev server, or run yours on another port (--port 8082 + adb reverse tcp:8081 tcp:8082). | | No events at all on Android | Permission not granted, or emulator has no Play services. | Check requestPermissions() resolved true; use a Google APIs emulator image. | | Events stop when the app is backgrounded (iOS) | UIBackgroundModes: location missing. | Add the key — the library emits an onError warning saying exactly this. | | Events stop when the app is backgrounded (Android) | Background location not granted. | The user must pick "Allow all the time", not "While using the app". | | Same coordinates repeating | Device is stationary — the fix is genuinely unchanged. | Expected. Compare arrival times, not loc.timestamp. | | Tracking dies when the app is swiped away | By design in v1. | See the capability matrix. |

Security & privacy

What this package does — and deliberately doesn't do — with your users' location:

  • No network. No storage. Location data goes from the OS straight to your JS callback. Nothing is uploaded, cached, or written to disk by this library.
  • No location logging. Coordinates never appear in logcat / console output, so they can't leak through device logs.
  • Service is not exported. No other app can start, stop, or feed options into the tracking service.
  • Permission-guarded. startTracking() rejects with PERMISSION_DENIED if location permission is missing (also prevents the Android 14 foreground-service crash).
  • Spoof detection surface. Every fix carries fromMockProvider so you can flag faked locations yourself.
  • Input clamping. Malformed options (negative intervals etc.) are clamped natively instead of crashing the service.
  • No zombie tracking. If the React instance goes away, the Android service is stopped and the context reference released — no GPS burning with nobody listening, no leaked contexts.

Contributing

License

MIT


Made with create-react-native-library