Skip to main content

React Native SDK Examples & Troubleshooting

Quickstart​

The address-string method keeps timeout and coordinates as positional arguments; delivery options are the fifth argument.

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

await DoorstepAI.init('Tracking...', 'Tracking your delivery');
DoorstepAI.setApiKey('your_api_key');

const foreground = await DoorstepAI.requestPermissions([
'locationWhenInUse',
'motionFitness',
'bluetoothScan',
]);
if (foreground.statuses.locationWhenInUse?.state === 'granted') {
await DoorstepAI.requestPermissions(['locationAlways']);
}

await DoorstepAI.startDeliveryByAddressString(
'123 Main St, Apt 4B, San Francisco, CA 94102',
'delivery_12345',
1800,
{ lat: 37.7749, lng: -122.4194 },
{
customerId: 'customer_42',
driverId: 'driver_7',
},
);

await DoorstepAI.newEvent('taking_pod', 'delivery_12345');
await DoorstepAI.markDropoff('delivery_12345', 'pod');
await DoorstepAI.stopDelivery('delivery_12345');

Full test-app component​

The component below wires those calls to a UI for manual testing. RootDoorstepAI handles init, API key, and permissions for you:

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

export default function App() {
return (
<View style={styles.container}>
<RootDoorstepAI
apiKey="some_api_key"
notificationTitle="Tracking..."
notificationText="Tracking your delivery"
/>
<Button
title="Start Delivery"
onPress={() => {
DoorstepAI.startDeliveryByPlaceID('destination_place_id', 'id_1');
}}
/>

<View style={styles.spacer} />
<Button
title="Mark Dropoff"
onPress={() => {
DoorstepAI.markDropoff('id_1', 'pod');
}}
/>
<View style={styles.spacer} />
<Button
title="Stop Delivery"
onPress={() => {
DoorstepAI.stopDelivery('id_1');
}}
/>
<View style={styles.spacer} />
<Button
title="New Event"
onPress={() => {
// Records a custom event; pass an optional epoch-seconds timestamp to backdate it
DoorstepAI.newEvent('event_name', 'id_1');
}}
/>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
spacer: {
height: 20,
},
});

Route-managed lifecycle​

const listener = DoorstepAI.addGeofenceSessionListener((event) => {
console.log(event.deliveryId, event.type, event.reason);
});

await DoorstepAI.startRouteGeofencing(
[
{
deliveryId: 'delivery_12345',
address: '123 Main St, San Francisco, CA',
latitude: 37.7749,
longitude: -122.4194,
customerId: 'customer_42',
driverId: 'driver_7',
},
{
deliveryId: 'delivery_67890',
address: '500 Market St, San Francisco, CA',
latitude: 37.7899,
longitude: -122.4009,
radiusMeters: 200,
},
],
{
defaultRadiusMeters: 250,
timeoutSeconds: 1800,
manualForeground: false, // Android only
},
);

// Call when the driver completes the stop. A later EXIT can then stop it.
await DoorstepAI.markDropoff('delivery_12345', 'pod');

// On dispatch changes, pass the complete replacement list.
await DoorstepAI.updateRouteStops(updatedStops);

// At logout or shift end:
await DoorstepAI.stopRouteGeofencing();
listener?.remove();

Call resumeRouteGeofencingIfNeeded() after initialization and API-key setup on every launch. Background route events require Always/background location permission.

Permission-state handling​

Use the wrapper's canonical buckets and detailed native states instead of branching on PermissionsAndroid strings:

const { statuses, unsupported } = await DoorstepAI.checkPermissions();

switch (statuses.locationAlways?.state) {
case 'granted':
break;
case 'whenInUseOnly':
// Explain the background need, then run the second permission phase.
await DoorstepAI.requestPermissions(['locationAlways']);
break;
case 'permanentlyDenied':
case 'restricted':
// Direct the user to the operating-system Settings screen.
break;
case 'notDeclared':
// Fix AndroidManifest.xml or Info.plist in the host build.
break;
}

console.log('Buckets unavailable on this platform:', unsupported);

The request result reports requested, alreadyDetermined, unavailable, and deferred, plus the settled statuses. On Android, locationAlways is always a separate request after foreground location. On iOS, request from a user-initiated flow rather than app launch.

See React Native Permissions for every accepted bucket name, detailed result field, Android-only helper, and the full staged flow.

Troubleshooting​

SDK initialization fails​

  • Confirm init completes before setApiKey and before the first start.
  • Confirm the API key is present and belongs to the expected environment.
  • Wrap each asynchronous call in try/catch and log the rejected error.

A permission is unavailable or undeclared​

  • unavailable means the OS, version, or hardware does not expose that bucket. Android-only Wi-Fi and notification buckets are expected to be unavailable on iOS.
  • notDeclared means the host build is missing an Android manifest entry or iOS usage-description key. Fix the build; prompting again cannot solve it.

Background tracking or route events stop​

Check statuses.locationAlways.state. whenInUseOnly is foreground authorization, not a background grant. Also verify Android's foreground service is running, or pass manualForeground: true only when the host already owns one.

Start arguments fail TypeScript checks​

Do not put coordinates into StartDeliveryOptions. For startDeliveryByAddressString, the order is (address, deliveryId, timeoutSeconds?, coordinates?, options?); place/address variants have their corresponding published positional signatures.

Release checklist​

  • Native manifests contain every permission your whitelist requests
  • Foreground and background permission phases are tested on both platforms
  • Manual start, event, mark, and stop calls succeed
  • Route state resumes after process relaunch
  • Listener subscriptions are removed during component teardown