react-native-location-service
v1.0.1
Published
Clean-room native background location tracking engine with 2D Kalman filter, Miniball geofencing, and OEM battery-saver management.
Maintainers
Readme
react-native-location-service
A high-performance, 100% clean-room, open-source background location tracking and geofencing engine for React Native.
Engineered from the ground up to provide infinite background tracking, 2D Kalman filtering for GPS jitter dampening, Welzl's Miniball algorithm for drift-free stationary geofencing, and hardware-level OEM task-killer workarounds (Xiaomi, Samsung, Huawei, Oppo, Vivo).
Table of Contents
- Architecture Overview
- Key Features
- Quick Start
- Installation
- Core Concepts & Heuristics
- API Reference
- Event Subscriptions
- Offline SQLite Queue & HTTP Sync
- Comparison with Alternatives
- Documentation Index
- License
Architecture Overview
flowchart TD
subgraph React Native JavaScript Layer
JS[App Code] --> LS[LocationService API]
LS --> DS[DeviceSettings Helper]
LS --> EE[NativeEventEmitter]
end
subgraph Native Bridge Layer
LS -- Autolinked --> Bridge[RNLocationService Bridge]
Bridge <--> EE
end
subgraph iOS Native Engine
Bridge --> LE_IOS[LocationEngine iOS]
LE_IOS --> CL[CoreLocation Manager]
LE_IOS --> CM[CoreMotion Activity]
LE_IOS --> KF_IOS[KalmanFilter 2D]
LE_IOS --> MB_IOS[Miniball Circumcircle]
LE_IOS --> SQL_IOS[(SQLite3 Encrypted Store)]
LE_IOS --> HTTP_IOS[NSURLSession Sync Engine]
end
subgraph Android Native Engine
Bridge --> LE_AND[LocationEngine Android]
LE_AND --> FGS[TrackingService Foreground Service]
FGS --> FLP[Google FusedLocationProviderClient]
FGS --> KF_AND[KalmanFilter 2D]
FGS --> MB_AND[Miniball Circumcircle]
FGS --> OEM[DeviceSettings OEM Intenter]
FGS --> SQL_AND[(SQLite Database Store)]
FGS --> HTTP_AND[OkHttp3 Background Sync]
endKey Features
- 🛰️ True Continuous Background Execution: Stays alive when the app is minimized, screen is locked, or system is under memory pressure using iOS Background Location and Android Foreground Services.
- 📐 State-Space Kalman Filter: Smooths GPS multi-path jitter and indoor wander using dynamic measurement error covariance ($R = \sigma^2$).
- ⭕ Welzl's Miniball Bounding Circle: Automatically calculates the exact minimum enclosing circumcircle around stationary coordinate clusters, eliminating false geofence exit triggers.
- 🔋 OEM "Don't Kill My App" Whitelist Manager: Direct Intent dispatching for aggressive task killers on Xiaomi MIUI/HyperOS, Huawei EMUI, Samsung OneUI, Oppo ColorOS, and Vivo FuntouchOS.
- 💾 Resilient Offline SQLite Store: Zero location loss in tunnels or out-of-coverage areas; fixes queue locally and flush automatically upon reconnection.
- 🔄 Pace-Driven Battery Throttling: Automatically switches between low-power stationary monitoring and high-accuracy moving tracking using hardware motion coprocessors.
- ⚡ Zero Setup RN Autolinking: Compatible with standard React Native (0.60+) and Expo Bare Workflow without manual podspec or Gradle hacks.
Quick Start
import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import LocationService, { Location, MotionChangeEvent } from 'react-native-location-service';
export default function TrackingScreen() {
const [currentLocation, setCurrentLocation] = useState<Location | null>(null);
const [isMoving, setIsMoving] = useState<boolean>(false);
useEffect(() => {
// 1. Subscribe to Kalman-smoothed location fixes
const locationSub = LocationService.onLocation((location) => {
console.log('[Location Fix]', location.coords.latitude, location.coords.longitude);
setCurrentLocation(location);
});
// 2. Subscribe to motion state changes (Stationary vs Moving)
const motionSub = LocationService.onMotionChange((event: MotionChangeEvent) => {
console.log('[Motion Changed] isMoving:', event.isMoving);
setIsMoving(event.isMoving);
});
// 3. Configure and launch the engine
LocationService.ready({
distanceFilter: 10, // Minimum meters before firing update
desiredAccuracy: 'high', // Best accuracy
stationaryRadius: 25, // Bound by Miniball algorithm
stopTimeout: 5, // Minutes before entering stationary state
autoSync: true, // Automatically upload to server
url: 'https://api.example.com/locations',
headers: {
'Authorization': 'Bearer YOUR_TOKEN'
}
}).then((state) => {
console.log('LocationEngine Ready:', state);
LocationService.start();
});
// 4. Android: Handle OEM task killer whitelist dialogs
LocationService.deviceSettings.isIgnoringBatteryOptimizations().then((isIgnoring) => {
if (!isIgnoring) {
LocationService.deviceSettings.showIgnoreBatteryOptimizations();
}
});
return () => {
locationSub.remove();
motionSub.remove();
};
}, []);
return (
<View style={styles.container}>
<Text style={styles.title}>react-native-location-service</Text>
<Text>Status: {isMoving ? 'Moving 🚗' : 'Stationary 🛑'}</Text>
<Text>Lat: {currentLocation?.coords.latitude.toFixed(6) ?? '...'}</Text>
<Text>Lng: {currentLocation?.coords.longitude.toFixed(6) ?? '...'}</Text>
<Text>Accuracy: {currentLocation?.coords.accuracy.toFixed(1) ?? '...'} m</Text>
<Text>Odometer: {((currentLocation?.odometer ?? 0) / 1000).toFixed(2)} km</Text>
<Button title="Toggle Pace" onPress={() => LocationService.changePace(!isMoving)} />
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center', gap: 10 }
});Installation
npm install react-native-location-service
# or with yarn:
yarn add react-native-location-serviceiOS Setup
- Install CocoaPods:
cd ios && pod install- Add permission usage strings to your
ios/YourApp/Info.plist:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We need your location in the background to record delivery routes.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need your location while using the app.</string>
<key>NSMotionUsageDescription</key>
<string>We use motion detection to automatically stop tracking when stationary.</string>
<key>UIBackgroundModes</key>
<array>
<string>location</string>
<string>fetch</string>
<string>processing</string>
</array>Android Setup
- Declare required permissions in
android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
</manifest>- Ensure your
compileSdkVersionandtargetSdkVersionare set to at least 34 or 35 inandroid/build.gradle.
Core Concepts & Heuristics
1. 2D Linear Kalman Filter
Standard GPS receivers suffer from multi-path reflections and atmospheric interference, producing sudden "jumps" of 10–50 meters even when the user is standing still.
react-native-location-service implements a discrete-time 2D state-space Kalman filter running directly in native code:
- State Vector: $\mathbf{x}_k = [\text{lat}_k, \text{lng}_k]^T$
- Measurement Error Covariance ($R_k$): Dynamically assigned as $R_k = \sigma_{\text{accuracy}}^2$.
- Process Noise ($Q$): Models user acceleration variance ($3.0\text{ m/s}^2$).
- Kalman Gain: $$K_k = \frac{P_k^-}{P_k^- + R_k}$$ When GPS accuracy is poor (large $R$), $K_k \to 0$, giving heavy weight to previous trajectory and ignoring erratic jumps. When accuracy is sharp, $K_k \to 1$.
2. Welzl's Miniball Stationary Geofence
When a user stops moving, GPS fixes drift in a cluster. Traditional libraries center a circle on the last fix, causing the next drift point to immediately trigger a false "exit" event.
react-native-location-service applies Welzl's Randomized Minimum Enclosing Circle algorithm:
- Coordinates are projected into a local Cartesian tangent plane (meters).
- The exact minimal enclosing circle containing the last 10 fixes is calculated in $O(N)$ expected time.
- The circular geofence radius is set to $\max(\text{stationaryRadius}, R_{\text{miniball}})$.
- The user must genuinely exit this bounding boundary to trigger a moving transition.
3. OEM Battery-Saver & Whitelist Manager
On Asian Android devices (Xiaomi, Samsung, Huawei, Oppo, Vivo), proprietary OS power managers terminate background services regardless of standard Android foreground service declarations.
The built-in LocationService.deviceSettings helper detects the hardware manufacturer and dispatches targeted system intents:
- Xiaomi (MIUI / HyperOS): Opens
HiddenAppsConfigActivityand Auto-Start management. - Huawei (EMUI / HarmonyOS): Opens
StartupNormalAppListActivityand Protected Apps. - Samsung (OneUI): Opens Smart Manager
BatteryActivity. - Oppo / Realme (ColorOS): Opens SafeCenter Auto-startup list.
- Vivo / iQOO (FuntouchOS): Opens Background StartUp Manager.
API Reference
Core Engine Methods
| Method | Parameters | Return Type | Description |
| :--- | :--- | :--- | :--- |
| ready(config) | LocationConfig | Promise<State> | Configures the engine and requests permissions. |
| start() | None | Promise<boolean> | Starts background location tracking and services. |
| stop() | None | Promise<boolean> | Halts tracking and terminates background services. |
| changePace(isMoving) | boolean | Promise<boolean> | Manually toggles between stationary and moving tracking. |
| getState() | None | Promise<State> | Returns the current engine configuration and running state. |
| getCurrentPosition(opts) | { timeout?: number } | Promise<Location> | Retrieves a single immediate location fix. |
| getOdometer() | None | Promise<number> | Returns total accumulated distance in meters. |
| setOdometer(value) | number | Promise<Location> | Resets or adjusts the odometer value. |
| addGeofence(geofence) | Geofence | Promise<boolean> | Registers a circular region for boundary monitoring. |
| removeGeofence(id) | string | Promise<boolean> | Unregisters a geofence by identifier. |
| getGeofences() | None | Promise<Geofence[]> | Returns list of all active geofences. |
| getLocations() | None | Promise<Location[]> | Retrieves all locally queued offline location fixes. |
| sync() | None | Promise<Location[]> | Forces immediate HTTP upload of queued locations. |
| destroyLocations() | None | Promise<boolean> | Clears local SQLite location database. |
| isPowerSaveMode() | None | Promise<boolean> | Returns whether the OS is in low-power battery saver mode. |
Event Subscriptions
All events return an EmitterSubscription. Call .remove() when unmounting:
// Location Fixes
const sub = LocationService.onLocation((location: Location) => {});
// Motion State Change (Stationary <-> Moving)
const sub = LocationService.onMotionChange((event: MotionChangeEvent) => {});
// Activity Recognition (still, on_foot, in_vehicle, on_bicycle)
const sub = LocationService.onActivityChange((event: ActivityChangeEvent) => {});
// Geofence Entry / Exit
const sub = LocationService.onGeofence((event: GeofenceEvent) => {});
// HTTP Upload Response
const sub = LocationService.onHttp((event: HttpEvent) => {});
// Location Provider Change (GPS / Network toggles)
const sub = LocationService.onProviderChange((event: ProviderChangeEvent) => {});
// Low Power Mode Toggle
const sub = LocationService.onPowerSaveChange((isPowerSave: boolean) => {});Comparison with Alternatives
| Feature | react-native-location-service | TransistorSoft Background Geolocation | react-native-geolocation-service | expo-location |
| :--- | :---: | :---: | :---: | :---: |
| Open Source | 100% MIT | Closed-source binary | MIT | MIT |
| Licensing Cost | Free ($0) | Paid per-app license | Free | Free |
| Infinite Background Tracking | ✅ Yes | ✅ Yes | ❌ Killed in background | ⚠️ Partial (TaskWorker) |
| 2D Kalman Filter | ✅ Built-in | ✅ Proprietary blob | ❌ No | ❌ No |
| Miniball Circumcircle Geofence | ✅ Built-in | ✅ Proprietary blob | ❌ No | ❌ No |
| OEM Battery Killer Manager | ✅ Built-in | ✅ Proprietary blob | ❌ No | ❌ No |
| Encrypted Offline SQLite Queue | ✅ Built-in | ✅ Built-in | ❌ No | ❌ No |
| Autolinking (Zero Podfile Hacks)| ✅ Yes | ❌ Requires custom CocoaPod | ✅ Yes | ✅ Yes |
Documentation Index
Explore our comprehensive technical guides in the docs/ directory:
- 📖 Architecture Deep Dive - Internal threading, lifecycle, native subsystems, and database schema.
- 📐 Heuristics & Algorithms - Mathematical formulations of the 2D Kalman filter, Welzl's algorithm, and OEM intent matrices.
- 📚 Full API Reference - Complete types, parameters, options, and event specifications.
- 🚀 Integration Guide - Bare React Native, New Architecture, Expo Bare, and backend receiver templates.
License
This project is licensed under the MIT License.
