API reference
Everything on this page comes from the package's public TypeScript spec. The same API works on Android and iOS. Where a method behaves differently per platform, it says so.
import LocationTracker, {
type Spec,
type Status,
type LocationEvent,
type SyncEvent,
type TrackingProfile,
} from 'react-native-background-location-pro-eval';
The default export is the LocationTracker module, typed as Spec. All methods are synchronous JSI calls that return immediately. Results arrive through events or the next getStatus().
Methods at a glance
| Method | Purpose |
|---|---|
startTracking(serverUrl) | Start tracking and syncing to your backend |
stopTracking() | Stop tracking. Pending points stay on the device. |
getStatus() | Synchronous snapshot of all tracker state |
syncNow() | Force an immediate upload attempt |
setTrackingProfile(profile) | Change accuracy and battery trade-off live |
setSyncPolicy(batchSize, maxBackoffMs, maxAttempts) | Tune batching, backoff and dead-lettering |
setDemoMode(enabled) | Sync every 5 s instead of 30 s |
requestLocationPermission(always) | Ask for When-In-Use or Always location |
setMotionPermissionOptIn(enabled) | Android: opt in to Activity Recognition |
requestIgnoreBatteryOptimizations() | Ask to be exempt from battery restrictions |
openExactAlarmSettings() | Open the settings page needed for reliable recovery |
onLocationUpdate(handler) | Subscribe to new points |
onSyncStatus(handler) | Subscribe to sync, connectivity and motion changes |
Tracking
startTracking()
startTracking(serverUrl: string): void
Persists the server URL, marks tracking as desired, starts the location engine and arms the watchdog that revives tracking after process death or reboot.
| Parameter | Type | Description |
|---|---|---|
serverUrl | string | Base URL of your backend, without a trailing path. Points are posted to {serverUrl}/api/location/track. |
LocationTracker.startTracking('https://api.example.com');
- Call it while the app is in the foreground. Android does not allow a location foreground service to start from the background.
- Calling it again while tracking updates the server URL.
- If permission is missing, tracking does not start and
messagebegins with[permission_denied].
See Tracking lifecycle.
stopTracking()
stopTracking(): void
Clears the desired flag, cancels the watchdog and stops collection after a final flush. Buffered points stay on the device and upload on the next startTracking().
LocationTracker.stopTracking();
getStatus()
getStatus(): Status
Returns a Status snapshot. It is a synchronous JSI call that reads in-memory native state and never blocks on I/O, so it is safe to call on every render or on a timer.
const {running, pending, authorization} = LocationTracker.getStatus();
syncNow()
syncNow(): void
Forces an immediate sync attempt instead of waiting for the next tick. It is still skipped while offline. If tracking is not running, message becomes [not_running] start tracking to sync.
LocationTracker.syncNow();
Configuration
setTrackingProfile()
setTrackingProfile(profile: string): void // pass a TrackingProfile value
Persisted and applied live. No restart needed. Unknown values are rejected with an [invalid_profile] status and the current profile is kept.
const profile: TrackingProfile = 'balanced';
LocationTracker.setTrackingProfile(profile);
See TrackingProfile for the trade-offs.
setSyncPolicy()
setSyncPolicy(batchSize: number, maxBackoffMs: number, maxAttempts: number): void
Persisted. Controls how pending points are uploaded.
| Parameter | Type | Description |
|---|---|---|
batchSize | number | Maximum points per request. |
maxBackoffMs | number | Upper bound for the jittered exponential backoff between retries, in milliseconds. |
maxAttempts | number | How many non-retryable 4xx rejections a batch may get before it is dead-lettered. 0 means keep retrying forever. |
// 100 points per batch, back off at most 5 minutes, dead-letter after 5 rejections
LocationTracker.setSyncPolicy(100, 300_000, 5);
See Offline tracking and sync.
setDemoMode()
setDemoMode(enabled: boolean): void
Persisted. Switches the sync tick between 5 seconds (demo) and 30 seconds (default). Batch size and idempotency are unchanged. Useful for live maps and demos. Leave it off in production to save battery and data.
LocationTracker.setDemoMode(__DEV__);
Permissions and system settings
requestLocationPermission()
requestLocationPermission(always: boolean): void
Requests When-In-Use (foreground) location first. With always = true, it escalates to Always (background) once When-In-Use is granted, or opens the app's Settings page when the OS will no longer show the prompt.
| Parameter | Type | Description |
|---|---|---|
always | boolean | true to also request background (Always) location. Required for tracking after the app is killed. |
LocationTracker.requestLocationPermission(true);
The result is not returned. Read getStatus().authorization, or listen to onSyncStatus, after the user responds. On both platforms the OS shows the Always step as a separate prompt or Settings screen. Explain why you need it before calling this. See Android setup and iOS setup.
setMotionPermissionOptIn()
setMotionPermissionOptIn(enabled: boolean): void
Android only. Opts in to the Activity Recognition permission, which gives the adaptive_low_power motion gate an extra signal. It is a no-op on iOS, kept so the JS contract matches. On iOS, Motion & Fitness is requested on demand by the adaptive_low_power profile.
import {Platform} from 'react-native';
if (Platform.OS === 'android') {
LocationTracker.setMotionPermissionOptIn(true);
}
requestIgnoreBatteryOptimizations()
requestIgnoreBatteryOptimizations(): void
- Android: asks the user to exempt the app from battery optimizations, which improves survival on aggressive vendor ROMs.
- iOS: there is no such exemption. It opens the app's Settings page instead.
Check getStatus().batteryUnrestricted afterwards.
openExactAlarmSettings()
openExactAlarmSettings(): void
- Android: opens the "Alarms & reminders" setting that allows exact alarms, which the watchdog uses to recover reliably.
- iOS: opens the app's Settings page, where Background App Refresh lives.
Check getStatus().exactAlarmsAllowed afterwards.
Events
Events are EventEmitter properties on the module. Call them with a handler. They return a subscription with a remove() method. Always remove subscriptions when your component unmounts.
const sub = LocationTracker.onLocationUpdate(handler);
// later
sub.remove();
onLocationUpdate
readonly onLocationUpdate: EventEmitter<LocationEvent>
Fires once per accepted fix, after it has been saved on the device. Payload: LocationEvent.
useEffect(() => {
const sub = LocationTracker.onLocationUpdate(point => {
setPath(prev => [...prev, {latitude: point.latitude, longitude: point.longitude}]);
});
return () => sub.remove();
}, []);
onSyncStatus
readonly onSyncStatus: EventEmitter<SyncEvent>
Fires whenever connectivity, the queue, the sync outcome or the motion gate changes. Payload: SyncEvent.
useEffect(() => {
const sub = LocationTracker.onSyncStatus(e => {
setBadge(e.online ? `${e.pending} pending` : 'offline');
});
return () => sub.remove();
}, []);
Events reach JS only while the JS runtime is alive. Tracking and syncing continue natively when the app is backgrounded or killed. When your app opens again, read getStatus() to catch up.
Types
TrackingProfile
type TrackingProfile = 'continuous_fitness' | 'balanced' | 'adaptive_low_power';
| Value | Behavior | Best for |
|---|---|---|
continuous_fitness | Never pauses on its own. 5 m distance filter until stopTracking(). | Running, cycling, workout recording |
balanced | Always subscribed, with a 20 m displacement gate. | Delivery and ride tracking where you want steady points |
adaptive_low_power (default) | balanced plus a motion gate that parks GPS while the device is stationary. If motion sensors fail, it falls back to balanced. It never pauses silently. | All-day and fleet tracking where battery matters |
LocationEvent
One accepted fix, pushed as soon as it is persisted.
| Field | Type | Description |
|---|---|---|
id | string | Unique point ID. Same as points[].id in the backend payload. |
latitude | number | Degrees. |
longitude | number | Degrees. |
accuracy | number | Horizontal accuracy radius in meters. |
speed | number | Meters per second. |
timestamp | number | Capture time in epoch milliseconds. |
pending | number | Points on the device not yet acknowledged by the server, including this one. |
SyncEvent
Pushed whenever connectivity, the queue or the sync outcome changes.
| Field | Type | Description |
|---|---|---|
running | boolean | The pipeline is collecting. |
online | boolean | The device has connectivity. |
pending | number | Points on the device not yet acknowledged. |
synced | number | Points acknowledged by the server. |
lastSyncAt | number | Epoch ms of the last successful sync. 0 if none. |
retryAttempt | number | Consecutive failures of the in-flight batch. 0 when healthy. |
nextRetryAt | number | Epoch ms of the next scheduled attempt. 0 when none is pending. |
motionGateActive | boolean | The motion gate has parked GPS because the device is stationary. |
motionState | string | stationary, walking, running, driving, cycling, moving, unknown or unsupported. |
message | string | Human-readable status. Errors start with a stable [code] prefix. See status codes. |
Status
The snapshot returned by getStatus().
Tracking state
| Field | Type | Description |
|---|---|---|
running | boolean | The pipeline is collecting right now. |
desired | boolean | Tracking was requested and not stopped. While true, the watchdog revives the pipeline. |
trackingProfile | string | The active TrackingProfile. |
lastLocationAt | number | Epoch ms of the last accepted fix. |
message | string | Human-readable status. Errors start with a [code] prefix. |
buildTier | string | Evaluation edition only: 'debug' while evaluating, 'release' when evaluation is not available in this build. |
Sync state
| Field | Type | Description |
|---|---|---|
online | boolean | The device has connectivity. |
pending | number | Points on the device not yet acknowledged. |
synced | number | Points acknowledged by the server. |
rejected | number | Points dead-lettered after maxAttempts non-retryable rejections. |
lastSyncAt | number | Epoch ms of the last successful sync. |
retryAttempt | number | Consecutive failures of the in-flight batch. |
nextRetryAt | number | Epoch ms of the next scheduled attempt. 0 when none. |
lastSyncError | string | The last sync failure, for example http_503 or TimedOut. Empty after a success. |
serverUrl | string | The URL passed to startTracking(). |
deviceId | string | Stable per-install ID, also sent as X-Device-Id. |
demoMode | boolean | Sync every 5 s instead of 30 s. |
Permissions and system health
| Field | Type | Description |
|---|---|---|
authorization | string | notDetermined, whenInUse, always, denied or restricted. |
preciseLocation | boolean | Precise (full accuracy) location is granted. |
exactAlarmsAllowed | boolean | Android: exact alarms are allowed. iOS: Background App Refresh is available. Either way, the recovery watchdog can run. |
batteryUnrestricted | boolean | Android: exempt from battery optimizations. iOS: Low Power Mode is off. |
motionPermission | string | granted, denied, notRequested or unavailable. |
motionPermissionOptIn | boolean | Android: the app opted in via setMotionPermissionOptIn(true). Always false on iOS. |
Motion gate
| Field | Type | Description |
|---|---|---|
motionGateActive | boolean | GPS is parked because the device is stationary. |
motionState | string | stationary, walking, running, driving, cycling, moving, unknown or unsupported. |
motionSource | string | Which sensor drives the gate, or none. |
Spec
The full module interface, exported for typing wrappers and mocks.
interface Spec extends TurboModule {
startTracking(serverUrl: string): void;
stopTracking(): void;
getStatus(): Status;
syncNow(): void;
setDemoMode(enabled: boolean): void;
requestIgnoreBatteryOptimizations(): void;
openExactAlarmSettings(): void;
requestLocationPermission(always: boolean): void;
setTrackingProfile(profile: string): void;
setMotionPermissionOptIn(enabled: boolean): void;
setSyncPolicy(batchSize: number, maxBackoffMs: number, maxAttempts: number): void;
readonly onLocationUpdate: EventEmitter<LocationEvent>;
readonly onSyncStatus: EventEmitter<SyncEvent>;
}
Mocking in Jest
import type {Spec, Status} from 'react-native-background-location-pro-eval';
const subscription = {remove: jest.fn()};
const mock: Partial<Record<keyof Spec, unknown>> = {
startTracking: jest.fn(),
stopTracking: jest.fn(),
syncNow: jest.fn(),
setDemoMode: jest.fn(),
setTrackingProfile: jest.fn(),
setSyncPolicy: jest.fn(),
setMotionPermissionOptIn: jest.fn(),
requestLocationPermission: jest.fn(),
requestIgnoreBatteryOptimizations: jest.fn(),
openExactAlarmSettings: jest.fn(),
getStatus: jest.fn((): Partial<Status> => ({running: false, desired: false, pending: 0})),
onLocationUpdate: jest.fn(() => subscription),
onSyncStatus: jest.fn(() => subscription),
};
export default mock;