@santoshpk/react-native-background-location-tracking
v0.1.0
Published
Continuous background & foreground location tracking for React Native (new arch / TurboModule)
Maintainers
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.
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.
Installation
npm install @santoshpk/react-native-background-location-trackingNew architecture only. This library is a TurboModule and requires the React Native new architecture (default since RN 0.76). There is no old-architecture / bridge fallback, and none is planned — if you're on the legacy architecture, upgrade first.
Android setup
Permissions and the <service> entry are merged from the library manifest automatically. At runtime, requestPermissions() walks the required prompts in order:
ACCESS_FINE_LOCATIONPOST_NOTIFICATIONS(Android 13+)ACCESS_BACKGROUND_LOCATION— user must pick "Allow all the time" (Android 10+)
Non-negotiable OS facts:
- Android 14+ requires the
locationforeground-service type (already declared here). - The persistent notification cannot be hidden — it is required by the OS.
iOS setup
Add to Info.plist:
<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>Background tracking needs the user to grant "Always" authorization. The module sets allowsBackgroundLocationUpdates = true and pausesLocationUpdatesAutomatically = false.
Note: this library never fakes background execution with silent-audio/VoIP tricks — those get you rejected from the App Store.
Usage
import {
requestPermissions,
startTracking,
stopTracking,
getCurrentLocation,
isTracking,
onLocation,
onError,
} from '@santoshpk/react-native-background-location-tracking';
// 1. Subscribe BEFORE starting, so you don't miss the first fixes.
// `sub` is a subscription handle — keep it to unsubscribe later.
const sub = onLocation((loc) => {
// Fires on every native update with fresh LocationData:
console.log(loc.latitude, loc.longitude, loc.accuracy);
});
const errSub = onError((err) => console.warn(err.message));
// 2. Request permissions, then start.
// The rationale is optional — Android shows it when the user previously
// said no. On iOS your "why" lives in the Info.plist usage strings instead.
await requestPermissions({
title: 'Location needed',
message: 'MyApp uses your location to record your route.',
buttonPositive: 'OK',
});
await startTracking({
interval: 5000, // ms (Android)
fastestInterval: 5000, // ms (Android)
distanceFilter: 0, // metres
accuracy: 'high', // 'high' | 'balanced' | 'low' | 'passive'
notificationTitle: 'Tracking active',
notificationText: 'Recording your location',
notificationChannelName: 'Location tracking',
});
// One-shot helpers, independent of the stream:
const here = await getCurrentLocation(); // always resolves or rejects, never hangs
const active = await isTracking();
// 3. Cleanup — BOTH steps:
stopTracking(); // stops native GPS, the Android service + notification
sub.remove(); // detaches your JS listener (else it leaks and keeps firing)
errSub.remove();How events flow
Native side emits, JS side listens — one-way:
startTracking(options)
Android: FusedLocationProviderClient fires every `interval` ms
iOS: CLLocationManager fires on movement (distanceFilter throttles)
↓
native emits the 'location' event
↓
onLocation(callback) fires with LocationDataCleanup 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. |
In React, tie the listener to component lifetime:
useEffect(() => {
const sub = onLocation(handleLocation);
return () => sub.remove(); // unmount = unsubscribe
}, []);Platform nuance: Android interval is timer-based — a fix every N ms even when stationary. iOS has no interval; CoreLocation is movement-driven, so a perfectly still device emits fewer events. Use distanceFilter to throttle iOS. OS design, not a bug.
API
| Function | Description |
|----------|-------------|
| requestPermissions(rationale?) | Request location (+ notification + background on Android). Optional rationale { title, message, buttonPositive } explains why — shown by Android when needed. Resolves true when foreground location granted (precise or approximate). iOS no-op — 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>; rejects if unavailable. |
| isTracking() | Returns Promise<boolean>. |
| onLocation(cb) | Subscribe to updates. Returns a subscription — call .remove(). |
| onError(cb) | Subscribe to warnings/errors. Returns a subscription. |
LocationData: { latitude, longitude, accuracy, altitude, speed, course, timestamp, fromMockProvider }.
Call requestPermissions() before startTracking(). On Android it walks the required runtime prompts in order (fine → notifications → background "Allow all the time"). On iOS the "Always" prompt fires when startTracking() runs. See example/src/App.tsx.
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 withPERMISSION_DENIEDif location permission is missing (also prevents the Android 14 foreground-service crash). - Spoof detection surface. Every fix carries
fromMockProviderso 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
