Skip to content

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 unmount
sub.remove();

Location & motion

onLocation

function onLocation(
success: (location: Location) => void,
failure?: (errorCode: number) => void,
): Subscription

Fires 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 invokedonLocation() has no wired error path. If you need fix errors, use watchPosition()’s failure callback instead.

FieldTypeDescription
uuidstringUnique record id.
timestampstringISO-8601 UTC.
agenumberOptional. Fix age in ms at the time it was processed.
odometernumberCumulative odometer reading, metres.
coordsobject{ latitude, longitude, accuracy, altitude?, altitude_accuracy?, speed?, speed_accuracy?, heading?, heading_accuracy?, ellipsoidal_altitude? }.
activityobject{ type, confidence } — see data types.
batteryobject{ level, is_charging }.
is_movingboolean | nullMotion-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.
samplebooleanOptional.
eventstringOptional.
extrasobjectOptional 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,
): Subscription

Fires whenever the automatic motion-state machine flips between moving and stationary, and also when you force a transition with changePace().

FieldTypeDescription
isMovingbooleantrue on the moving transition, false on the stationary transition.
locationobjectOptional — 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): Subscription

Fires 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.

FieldTypeDescription
locationobjectOptional. 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,
): Subscription

Fires 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().

FieldTypeDescription
statusnumberOne of the AUTHORIZATION_STATUS_* constants.
enabledbooleanWhether location services are on at all.
gpsbooleanGPS provider enabled.
networkbooleanNetwork provider enabled.
accuracyAuthorizationnumberOptional, 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): Subscription

Fires 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,
): Subscription

Fires 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.

FieldTypeDescription
successbooleantrue when status is 2xx.
statusnumberHTTP status code; 0 when the request never got a response at all (network error).
responseTextstringResponse 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): Subscription

Fires 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.

FieldTypeDescription
connectedbooleanValidated-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,
): Subscription

Fires 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).

FieldTypeDescription
successbooleanWhether the refresh succeeded.
statusnumberPresent on failure — the HTTP status the refresh request itself received.
accessTokenstringPresent on success — the new access token.
refreshTokenstring | nullPresent 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,
): Subscription

Fires 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.

FieldTypeDescription
identifierstringThe geofence’s identifier.
action'ENTER' | 'EXIT' | 'DWELL'The transition type.
locationobjectThe Location fix that triggered the transition.
extrasobjectOptional. 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,
): Subscription

Fires 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.

FieldTypeDescription
onobject[]Geofence objects newly registered with the OS.
offobject[]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();