Skip to main content

React Native SDK Usage & Integration

In-SDK Geofencing is the recommended way to integrate: hand the SDK your stops and it starts and stops tracking automatically as the driver enters and exits each one. Prefer to drive geofence enter/exit yourself? See Manual Geofencing below.

In-SDK Geofencing

Hand the SDK your stops and it auto-starts and stops a tracking session as the driver enters and exits each one.

This flow has four touchpoints in your driver app (Initialize SDK is shared with the manual flow below):

#When this happens in the driver appWhat you call
1Driver app launchesInitialize SDK
2Start geofencing and tracking all stopsStart Geofencing
3Driver confirms drop-off or takes proof of deliveryMark Drop-off
4Stop geofencing and tracking all stopsStop Geofencing

If the driver's route changes mid-shift, call Update Stops as well.

Permissions

Requires location permission. For background operation, also requires "Allow all the time" (Android ACCESS_BACKGROUND_LOCATION) / Always authorization (iOS); without it, geofence registration is skipped entirely. The SDK exposes a typed whitelist and the same eight detailed states on both native platforms — request foreground location first, then request background/Always location separately after your own explanation. See the dedicated React Native Permissions guide.

How It Works

  1. You pass the route as a DeliveryStop[]; the SDK geofences the nearest stops for the route's lifetime.
  2. On ENTER, it auto-starts a session for that stop (same as startDeliveryByAddressString).
  3. You call markDropoff when the driver completes the stop.
  4. On EXIT, the session stops only if markDropoff was called. Otherwise it runs until timeoutSeconds, so a false EXIT can't cut a delivery short.
  5. Every auto-start/stop emits a GeofenceSessionEvent you can subscribe to for your UI.

The SDK handles the rest automatically: it absorbs GPS jitter, tightens location cadence near stops to balance precision and battery, and transparently manages large stop lists. These are tuned via remote config, not in your app.

API Reference

MethodDescription
startRouteGeofencing(stops, options?)Begins geofencing for stops (DeliveryStop[]), tuned by options (RouteGeofenceOptions). Rejects if no stop has a deliveryId + coordinates
markDropoff(deliveryId, dropoffType)Marks a stop's drop-off, gating its geofence EXIT stop. Shared with the manual flow (see Mark Drop-off)
updateRouteStops(stops)Replaces the monitored stops (DeliveryStop[]) in place. A stop removed from the list is stopped immediately (tagged removed_from_route), not EXIT-driven and not gated on markDropoff. Does not extend the route lease
stopRouteGeofencing()Clears all geofences and stops active route sessions. Teardown is handed to the tracking service, so getMonitoredStops() can still show the old list for a moment
resumeRouteGeofencingIfNeeded()Restores geofencing from persisted state. Call on every app launch; no-op if no route is active
getMonitoredStops()Returns the stops currently registered (DeliveryStop[], empty array if no route is active). This is the SDK's intent registry, not the OS's registered-region set
addGeofenceSessionListener(listener)Subscribe to auto start/stop (GeofenceSessionEvent) events. Returns EmitterSubscription | undefined; call .remove() to unsubscribe

1. Initialize SDK

The easiest way to initialize the SDK is the RootDoorstepAI component, which handles initialization and permission requests automatically. RootDoorstepAI is optional as of the latest release. Data collection begins when you call startDelivery*, as long as you initialize the SDK and set the API key elsewhere.

App.js
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { DoorstepAI, RootDoorstepAI } from '@doorstepai/dropoff-sdk';

export default function App() {
return (
<View style={styles.container}>
<RootDoorstepAI
apiKey="your_api_key_here"
notificationTitle="Tracking..."
notificationText="Tracking your delivery"
permissions={['locationWhenInUse', 'motion', 'bluetooth']}
requestBackgroundLocation
/>
{/* Your app content */}
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
},
});

permissions is the permission whitelist. requestBackgroundLocation runs the separate location-Always phase after foreground location has been handled. Omit either prop when your app owns that request flow.

RootDoorstepAI requests permissions when it mounts. For a staged, user-initiated onboarding flow, initialize manually and use the permission APIs:

await DoorstepAI.init('Tracking...', 'Tracking your delivery'); // Android; iOS no-op
DoorstepAI.setApiKey('your_api_key_here'); // synchronous

init(notificationTitle?, notificationText?) returns Promise<void> and is idempotent on Android. setApiKey(apiKey) returns void; it does not expose native's shouldGetConfig option.

API Key Security

Store your API key securely using environment variables or a secure configuration service. Never hardcode API keys in production builds.


2. Start Geofencing

Call startRouteGeofencing once you have the driver's stops for the shift. Pass every stop up front, not one at a time:

import { DoorstepAI } from '@doorstepai/dropoff-sdk';

const stops = [
{ deliveryId: 'delivery_12345', address: '123 Main St, San Francisco, CA', latitude: 37.7749, longitude: -122.4194 },
{ deliveryId: 'delivery_67890', address: '500 Market St, San Francisco, CA', latitude: 37.7899, longitude: -122.4009 },
];

await DoorstepAI.startRouteGeofencing(stops, { defaultRadiusMeters: 250 });

3. Mark Drop-off

When the driver completes the delivery (takes a POD or confirms drop-off in-app), mark it. markDropoff is the preferred API:

The call is identical for both flows. With In-SDK Geofencing, marking drop-off also gates the automatic geofence EXIT stop for that delivery: the session keeps running until it's called (or the timeout backstop fires).

await DoorstepAI.markDropoff('delivery_12345', 'pod'); // or 'non_pod'

markDropoff parameters

ParameterTypeDescription
deliveryIdstringRequired. The session you're marking
dropoffType'pod' | 'non_pod''pod' (proof of delivery captured) or 'non_pod'

Custom Events

Use newEvent to record a custom event on a delivery. Pass an optional timestamp (epoch seconds) to backdate the event; omit it and the event is recorded at "now".

// Recorded at "now"
await DoorstepAI.newEvent('taking_pod', 'delivery_12345');

// Recorded at an explicit time
await DoorstepAI.newEvent('taking_pod', 'delivery_12345', 1720000000);

newEvent parameters

ParameterTypeDescription
eventNamestringRequired. The event to record
deliveryIdstringRequired. The session the event belongs to
timestampnumber?Epoch seconds the event occurred. When provided, the event is recorded at that time instead of "now"

4. Stop Geofencing

Call stopRouteGeofencing when the shift ends (or the driver logs out). It clears every geofence and stops any sessions still running, regardless of whether markDropoff was called:

await DoorstepAI.stopRouteGeofencing();

Update Stops

Call updateRouteStops when the driver's route changes mid-shift (a stop added, removed, or reordered). It diffs against the currently monitored stops instead of tearing everything down. A stop removed from the list is stopped immediately and tagged removed_from_route; unlike a geofence EXIT, this is not gated on markDropoff:

const updatedStops = [
{ deliveryId: 'delivery_12345', address: '123 Main St, San Francisco, CA', latitude: 37.7749, longitude: -122.4194 },
{ deliveryId: 'delivery_99999', address: '1 Market St, San Francisco, CA', latitude: 37.7936, longitude: -122.3965 },
];

await DoorstepAI.updateRouteStops(updatedStops);

Resuming After Relaunch

Not part of the standard flow above, but worth adding for reliability: route state is persisted, so geofencing survives process death and OS restarts. Call resumeRouteGeofencingIfNeeded() on every app launch, after <RootDoorstepAI /> has initialized the SDK (or after your own manual init). It's a no-op if no route is active, so it's safe to call unconditionally:

import { useEffect } from 'react';
import { DoorstepAI } from '@doorstepai/dropoff-sdk';

useEffect(() => {
DoorstepAI.resumeRouteGeofencingIfNeeded();
}, []);

Getting Monitored Stops

Call getMonitoredStops to read back the stops currently registered, useful for rehydrating your UI on launch or confirming a route is active:

const stops = await DoorstepAI.getMonitoredStops();
console.log(`${stops.length} stop(s) currently monitored`);

Types

DeliveryStop

A single stop on the route. deliveryId is the same id you pass to markDropoff and stopDelivery.

type DeliveryStop = {
deliveryId: string;
address: string;
latitude: number;
longitude: number;
radiusMeters?: number; // falls back to RouteGeofenceOptions.defaultRadiusMeters (250)
customerId?: string;
driverId?: string;
};

RouteGeofenceOptions

Tuning for the whole route. Every field is optional, so startRouteGeofencing(stops) works with no options.

type RouteGeofenceOptions = {
defaultRadiusMeters?: number; // default 250
timeoutSeconds?: number; // per auto-started session
manualForeground?: boolean; // Android only, ignored on iOS
};

GeofenceSessionEvent

type GeofenceSessionEventType = 'STARTED' | 'STOPPED';

type GeofenceSessionEvent = {
deliveryId: string;
type: GeofenceSessionEventType;
reason: string; // e.g. "geofence", "distance", "timeout", "removed_from_route", "route_cleared"
};

Observing Geofencing Events

Call addGeofenceSessionListener to subscribe to auto start/stop events so your UI can reflect them in real time. It returns a subscription; call .remove() to unsubscribe:

import { useEffect } from 'react';
import { DoorstepAI } from '@doorstepai/dropoff-sdk';

useEffect(() => {
const sub = DoorstepAI.addGeofenceSessionListener((event) => {
console.log(`Geofencing event: ${event.deliveryId} -> ${event.type} (${event.reason})`);
});
return () => sub?.remove();
}, []);

Manual Geofencing

If you'd rather drive geofence enter/exit yourself instead of using In-SDK Geofencing above, wire up these four touchpoints:

The DoorstepAI SDK has four touchpoints in your driver app. Wire up each one and you're done. Everything else on this page is reference detail for those four calls.

#When this happens in the driver appWhat you call
1App launchesInitialize SDK
2Driver enters the ≥250 m delivery geofenceStart Tracking
3Driver takes POD or confirms drop-off in-appMark Drop-off
4Driver exits the ≥250 m delivery geofenceStop Tracking
Permissions

Tracking needs runtime location permission on Android. Request it before the first delivery (see Permission Handling). On iOS the system prompts automatically; you can trigger them up front via Requesting Permissions Up-Front (iOS).


1. Initialize SDK

Initialize the SDK the same way as described in Initialize SDK above. This step is identical for both flows.


2. Start Tracking

Call startDelivery… when the driver enters the delivery geofence, not once inside the building. Use a unique deliveryId per session: a meaningful identifier you can correlate on your side. Wrap calls in try/catch to handle failures such as an invalid key or denied permissions.

Pick whichever address format you have (startDeliveryByPlaceID, startDeliveryByAddress, or startDeliveryByAddressString):

import { DoorstepAI } from '@doorstepai/dropoff-sdk';

// By Google Place ID
await DoorstepAI.startDeliveryByPlaceID('some_place_id', 'delivery_12345');

// By address components
const address = {
streetNumber: '123',
route: 'Main Street',
subPremise: 'Apt 4B',
locality: 'San Francisco',
administrativeAreaLevel1: 'CA',
postalCode: '94102',
};
await DoorstepAI.startDeliveryByAddress(address, 'delivery_12345');

// By single address string, with optional coordinates and knobs
await DoorstepAI.startDeliveryByAddressString(
'123 Main St, Apt 4B, San Francisco, CA 94102',
'delivery_12345',
1200,
{ lat: 37.7749, lng: -122.4194 },
{ customerId: 'customer_42', driverId: 'driver_7' }
);

Start parameters

Every start method takes a deliveryId. The current React Native API keeps timeoutSeconds as a positional argument and accepts an optional trailing StartDeliveryOptions object:

ParameterTypeDescription
deliveryIdstringRequired. Unique per session; correlate it on your side
timeoutSecondsnumber?Auto-stops tracking after this duration, a backstop if the exit geofence is missed
coordinatesLatLngObject?Separate positional argument for the address/address-string variants; pairs the text with coordinates resolved upstream
manualForegroundboolean?(Android only, no-op on iOS) When true, the SDK won't promote its tracking service to the foreground; your app must already run its own
customerIdstring?Optional customer identifier passed through to session creation
driverIdstring?Optional driver identifier passed through to session creation, for correlation on your backend
type StartDeliveryOptions = {
timeoutSeconds?: number;
manualForeground?: boolean; // Android only, no-op on iOS
customerId?: string;
driverId?: string;
};
Deprecated start methods

startDeliveryByPlusCode and startDeliveryByLatLng are deprecated. Use startDeliveryByAddressString / startDeliveryByAddress with coordinates instead. They remain for backwards compatibility:

await DoorstepAI.startDeliveryByPlusCode('some_plus_code', 'delivery_12345', 1200);

await DoorstepAI.startDeliveryByLatLng(37.7749, -122.4194, 'Apt 4B', 'delivery_12345', 1200);

On Android, the deprecated Plus Code and lat/lng native overloads do not accept manualForeground; that option is ignored for those two starts. The supported place/address methods expose it.


3. Mark Drop-off

Mark the drop-off the same way as described in Mark Drop-off above. This step is identical for both flows.


4. Stop Tracking

Call stopDelivery when the driver exits the delivery geofence, not inside the building:

await DoorstepAI.stopDelivery('delivery_12345');

That covers the full lifecycle. The sections below are reference for the calls above.


Advanced APIs

Requesting Permissions Up-Front (iOS)

Trigger the iOS Motion/Fitness + Location permission prompts before the first delivery:

import { Platform } from 'react-native';
import { DoorstepAI } from '@doorstepai/dropoff-sdk';

if (Platform.OS === 'ios') {
// Defaults to "Always" location authorization. Pass false for when-in-use.
await DoorstepAI.requestAllPermissions(true);
}
Optional and platform-specific public utilities

These methods are not required for the delivery lifecycle:

MethodAvailability
setDoorstepGoApiKey(apiKey): voidBoth platforms; separate Doorstep Go credential
debugStateJson(): Promise<string>iOS only; rejects on Android
retryGnssCallbacks(): Promise<boolean>Android only; rejects on iOS
configureRemoteLogging(options): voidiOS only; no-op on Android
enableDevMode(apiKey): Promise<boolean>Both platforms
validateDevModeAccess(): Promise<boolean>Android only; returns false on iOS
disableDevMode(): Promise<boolean>Android only; returns false on iOS

debugStateJson() is diagnostic and its JSON is intentionally unstable; log it for support, but never branch product behavior on it.


Best Practices

Error Handling

Wrap every SDK call in try/catch. See Start Tracking for why (invalid keys, denied permissions, etc. surface as thrown errors).

Lifecycle Management

The SDK continues tracking in the background regardless of app state. If you want to observe state changes, use AppState:

import React, { useEffect } from 'react';
import { AppState } from 'react-native';

const DeliveryManager = () => {
useEffect(() => {
const handleAppStateChange = (nextAppState) => {
// handle app state change
};

const subscription = AppState.addEventListener('change', handleAppStateChange);
return () => subscription?.remove();
}, []);

// Your delivery management logic
};

Permission Handling

Use the SDK's cross-platform permission buckets instead of branching on native permission strings. requestPermissions accepts either a bucket array or an options object. Location-always is deliberately a second phase because both operating systems require foreground authorization first.

import { DoorstepAI } from '@doorstepai/dropoff-sdk';

const foreground = await DoorstepAI.requestPermissions([
'locationWhenInUse',
'motionFitness',
'bluetoothScan',
'nearbyWifiDevices',
'notifications',
]);

if (foreground.statuses.locationWhenInUse?.state === 'granted') {
await DoorstepAI.requestPermissions({ permissions: ['locationAlways'] });
}

const current = await DoorstepAI.checkPermissions();
const location = await DoorstepAI.checkPermissions(['locationWhenInUse']);

The wrapper accepts both native spellings: motionFitness maps to Android's activityRecognition, and bluetoothScan maps to iOS bluetooth (and vice versa). The remaining names are locationWhenInUse, locationAlways, nearbyWifiDevices, and notifications; the last two are Android-only and appear in unsupported on iOS. Each statuses entry uses granted, notDetermined, whenInUseOnly, denied, permanentlyDenied, restricted, unavailable, or notDeclared.

For exact method signatures, bucket translation, result types, staged prompts, Android-only helpers, and troubleshooting, see React Native Permissions.

Next Steps

  1. 🔐 Review Permissions: buckets, states, and staged prompts
  2. 💡 View Examples: complete implementation examples