Events
Import the default export — every on* listener below is a property of it:
import BackgroundGeolocation from '@dc-bgeo/react-native-background-geolocation';Subscription model
Every on* method registers a callback and returns a Subscription:
interface Subscription { remove(): void;}Call .remove() on the returned object to detach that one listener. To tear
down every listener at once (including geofence listeners and the
watchPosition() wiring), call
removeListeners()
instead of removing them one at a time.
Events are only delivered while the JS context is running. A killed app, or one that hasn’t been launched since boot, cannot receive any of the events below — the native side keeps tracking, filtering, persisting, and uploading without you. See the boot & killed-app behavior guide for what happens instead, and the “Headless” line under each event for whether it also reaches the Android headless task while the JS context isn’t running.
locationerror is not a public on* method. It’s an internal event
stream that surfaces only through
watchPosition()’s
optional failure callback — there is no standalone
onLocationError()/removeListener pair for it.
const sub = BackgroundGeolocation.onLocation(location => { console.log('location', location.coords);});
// later, e.g. on unmountsub.remove();Location & motion
onLocation
function onLocation( success: (location: Location) => void, failure?: (errorCode: number) => void,): SubscriptionFires for every accepted location fix — both the ambient continuous stream
(whenever tracking is enabled) and, if you’ve separately called
watchPosition(), those
same fixes ride this same underlying native location event. watchPosition()
distinguishes its own fixes with an extras.watch marker and filters on it
internally, but onLocation() does not — if watchPosition() is active,
your onLocation() listeners also receive its fixes, unfiltered.
The failure parameter is accepted for signature compatibility but is
never invoked — onLocation() has no wired error path. If you need fix
errors, use watchPosition()’s failure callback instead.
| Field | Type | Description |
|---|---|---|
uuid | string | Unique record id. |
timestamp | string | ISO-8601 UTC. |
age | number | Optional. Fix age in ms at the time it was processed. |
odometer | number | Cumulative odometer reading, metres. |
coords | object | { latitude, longitude, accuracy, altitude?, altitude_accuracy?, speed?, speed_accuracy?, heading?, heading_accuracy?, ellipsoidal_altitude? }. |
activity | object | { type, confidence } — see data types. |
battery | object | { level, is_charging }. |
is_moving | boolean | null | Motion-state machine’s verdict at the time of this fix; null during a cold start’s unconfirmed-MOVING probing window (treat as false) — see Location. |
sample | boolean | Optional. |
event | string | Optional. |
extras | object | Optional passthrough — e.g. { watch: true } on watchPosition() fixes, { heartBeat: true }, { getCurrentPosition: true }. |
See Location for the full type.
Headless: No. High-frequency location fixes are deliberately excluded
from headless dispatch — forwarding every fix would spin up the Android
HeadlessJsTaskService (and a wakelock) per fix while tracking, draining the
battery and risking ANRs/foreground-service-start restrictions. Fixes taken
while the JS context isn’t running are still durably queued and uploaded by
the native HTTP store; they’re just not delivered as a headless JS event.
const sub = BackgroundGeolocation.onLocation( location => console.log('fix', location.coords, location.is_moving),);
// ...sub.remove();onMotionChange
function onMotionChange( callback: (event: { isMoving: boolean; location?: Location }) => void,): SubscriptionFires whenever the automatic motion-state machine flips between moving and
stationary, and also when you force a transition with
changePace().
| Field | Type | Description |
|---|---|---|
isMoving | boolean | true on the moving transition, false on the stationary transition. |
location | object | Optional — absent (Android) or null (iOS) on the first motionchange of a tracking session, before the first fix exists (the initial enterMoving probe fires from startTracking ahead of any location). Once a fix exists, the Location that triggered (or accompanies) the transition. |
react-native/src/types.ts’s MotionChangeEvent declares this as
location?: Location | null, covering both shapes — always guard before
dereferencing it.
Headless: Yes — motionchange is in the Android headless event set.
const sub = BackgroundGeolocation.onMotionChange(({ isMoving, location }) => { if (location) { console.log(isMoving ? 'started moving' : 'stopped', location.coords); } else { console.log(isMoving ? 'started moving' : 'stopped', '(no fix yet)'); }});
// ...sub.remove();onHeartbeat
function onHeartbeat(callback: (event: { location?: Location }) => void): SubscriptionFires every heartbeatInterval
seconds (default 60) unconditionally while tracking is enabled — not gated
behind a separate opt-in. Each firing also runs a safety check against the
stop-timeout countdown.
| Field | Type | Description |
|---|---|---|
location | object | Optional. The last known Location — absent if no fix has been taken yet in the current session. |
Headless: Yes — heartbeat is in the Android headless event set.
const sub = BackgroundGeolocation.onHeartbeat(({ location }) => { console.log('heartbeat', location?.coords);});
// ...sub.remove();Permissions & power
onProviderChange
function onProviderChange( callback: (event: { status: number; enabled: boolean; gps: boolean; network: boolean; accuracyAuthorization?: number; }) => void,): SubscriptionFires when the OS location-provider or authorization state changes — a
permission grant/revoke, GPS or network provider toggled, or Location
Services switched off entirely. This is the same shape returned by the
one-shot getProviderState().
| Field | Type | Description |
|---|---|---|
status | number | One of the AUTHORIZATION_STATUS_* constants. |
enabled | boolean | Whether location services are on at all. |
gps | boolean | GPS provider enabled. |
network | boolean | Network provider enabled. |
accuracyAuthorization | number | Optional, iOS only — ACCURACY_AUTHORIZATION_FULL/REDUCED. |
Headless: Yes — providerchange is in the Android headless event set.
const sub = BackgroundGeolocation.onProviderChange(({ status, enabled, gps }) => { console.log('provider', { status, enabled, gps });});
// ...sub.remove();onPowerSaveChange
function onPowerSaveChange(callback: (isPowerSaveMode: boolean) => void): SubscriptionFires when the OS power-saving state changes (Android battery saver, iOS Low Power Mode).
Payload quirk: the callback receives a bare boolean, not an event
object. Natively the engine emits { isPowerSaveMode: boolean }; the JS
facade unwraps it before invoking your listener (Transistor
callback parity). This unwrap only happens on the live-JS listener path — see
the Headless note below.
Headless: Yes — powersavechange is in the Android headless event set,
but the bare-boolean unwrap above is JS-listener-only. The headless task
receives the unwrapped native shape instead: { name: 'powersavechange', isPowerSaveMode: boolean } (per HeadlessEvent,
payload fields are flattened alongside name).
const sub = BackgroundGeolocation.onPowerSaveChange(isPowerSaveMode => { console.log('power save mode:', isPowerSaveMode);});
// ...sub.remove();HTTP & connectivity
onHttp
function onHttp( callback: (event: { success: boolean; status: number; responseText: string }) => void,): SubscriptionFires once per completed location-sync HTTP request — log uploads (see
the logging guide) do not emit this event.
A 401/403 that triggers a token refresh-and-retry emits two events:
one for the failed original request and one for the retried request.
| Field | Type | Description |
|---|---|---|
success | boolean | true when status is 2xx. |
status | number | HTTP status code; 0 when the request never got a response at all (network error). |
responseText | string | Response body, or the error message when status is 0 — truncated to 1024 characters. |
Headless: Yes — http is in the Android headless event set.
const sub = BackgroundGeolocation.onHttp(({ success, status, responseText }) => { if (!success) { console.warn('upload failed', status, responseText); }});
// ...sub.remove();onConnectivityChange
function onConnectivityChange(callback: (event: { connected: boolean }) => void): SubscriptionFires on validated-internet connectivity transitions — Android
NET_CAPABILITY_VALIDATED / iOS NWPath.satisfied — not raw radio/Wi-Fi
association. A captive-portal Wi-Fi network (connected to the AP, no real
internet) reports connected: false, not true.
A newly-registered listener also immediately receives the current connectivity state as a synthetic first delivery, so you don’t have to call anything else to learn the state at subscribe time — unless a real connectivity transition fires first, or the listener is already removed before that internal state check resolves, in which case the synthetic delivery is silently skipped. This replay is JS-side only and does not apply to the headless path.
| Field | Type | Description |
|---|---|---|
connected | boolean | Validated-internet reachability. |
Headless: Yes — connectivitychange is in the Android headless event
set. (The subscribe-time replay above never applies headlessly — headless
dispatch only ever carries real transitions.)
const sub = BackgroundGeolocation.onConnectivityChange(({ connected }) => { console.log('connected:', connected);});
// ...sub.remove();onAuthorization
function onAuthorization( callback: (event: { success: boolean; status?: number; accessToken?: string; refreshToken?: string | null; }) => void,): SubscriptionFires with the outcome of a native, killed-app-safe token refresh —
configured via config.authorization.
The uploader triggers this refresh on a 401/403 with no JS context
required, so this event is how your app learns a background refresh
happened (and should prefer
getAuthState() over its
own persisted tokens afterward).
| Field | Type | Description |
|---|---|---|
success | boolean | Whether the refresh succeeded. |
status | number | Present on failure — the HTTP status the refresh request itself received. |
accessToken | string | Present on success — the new access token. |
refreshToken | string | null | Present on success — the new refresh token, or null if the response didn’t rotate it. |
Headless: No. authorization is not present in the Android engine’s
HEADLESS_EVENTS set.
const sub = BackgroundGeolocation.onAuthorization(event => { if (event.success) { console.log('token refreshed', event.accessToken); } else { console.warn('token refresh failed', event.status); }});
// ...sub.remove();Geofencing
onGeofence
function onGeofence( callback: (event: { identifier: string; action: 'ENTER' | 'EXIT' | 'DWELL'; location: Location; extras?: object; }) => void,): SubscriptionFires on a transition for any geofence currently registered with the OS (the
proximity-sliced subset — see the
Geofencing guide). DWELL requires
notifyOnDwell + loiteringDelay on the geofence and is implemented
natively on Android; on iOS it’s a timer armed after ENTER and cancelled on
EXIT before it fires. Geofence transitions ride the same durable upload
queue as location records, so they’re delivered to your server even if the
app was killed when the transition happened.
| Field | Type | Description |
|---|---|---|
identifier | string | The geofence’s identifier. |
action | 'ENTER' | 'EXIT' | 'DWELL' | The transition type. |
location | object | The Location fix that triggered the transition. |
extras | object | Optional. The geofence’s own extras, echoed back. |
Headless: Yes — geofence is in the Android headless event set.
const sub = BackgroundGeolocation.onGeofence(({ identifier, action }) => { console.log(`geofence ${identifier}: ${action}`);});
// ...sub.remove();onGeofencesChange
function onGeofencesChange( callback: (event: { on: Geofence[]; off: Geofence[] }) => void,): SubscriptionFires when the proximity-sliced subset of geofences actually registered with
the OS changes — e.g. as you move and different geofences fall in or out of
geofenceProximityRadius.
This is a delta, not the full set: on is the geofences newly
registered, off is the geofences just unregistered. See the
Geofencing guide for the proximity-slicing
algorithm and platform registration budgets.
| Field | Type | Description |
|---|---|---|
on | object[] | Geofence objects newly registered with the OS. |
off | object[] | Geofence objects just unregistered. |
Headless: No. geofenceschange is not present in the Android engine’s
HEADLESS_EVENTS set — only the transition event (onGeofence) reaches the
headless task; a proximity-slice recompute does not.
const sub = BackgroundGeolocation.onGeofencesChange(({ on, off }) => { console.log(`+${on.length} geofences, -${off.length} geofences`);});
// ...sub.remove();