Capacitor SDK Usage & Integration
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.
Code samples are Next.js / TypeScript for concreteness. The
DoorstepAIcalls are identical across frameworks; translate only the imports and lifecycle hooks. See Installation for the framework-agnostic details.
| # | When this happens in the driver app | What you call |
|---|---|---|
| 1 | App launches | Initialize SDK |
| 2 | Driver enters the ≥250 m delivery geofence | Start Tracking |
| 3 | Driver takes POD or confirms drop-off in-app | Mark Drop-off |
| 4 | Driver exits the ≥250 m delivery geofence | Stop Tracking |
Tracking needs runtime permissions. See Permission Handling for details.
1. Initialize SDK
Initialize the SDK once when your app boots, from a top-level component, a service that runs on bootstrap, or a provider:
- Next.js / React: a top-level Client Component or provider (shown below)
- Angular:
AppComponent.ngOnInitor anAPP_INITIALIZERthat returns aPromise - Vue / Nuxt:
onMountedin your root component, or a plugin
Initialization is two separate calls: init(...) configures the SDK, and setApiKey(...) authenticates it. The API key is not passed to init. Always call init first, then setApiKey.
'use client';
import { useEffect, useState, type ReactNode } from 'react';
import { Capacitor } from '@capacitor/core';
import { DoorstepAI } from '@doorstepai/dropoff-capacitor';
export function DoorstepProvider({ children }: { children: ReactNode }) {
const [ready, setReady] = useState(false);
useEffect(() => {
if (!Capacitor.isNativePlatform()) {
setReady(true);
return;
}
(async () => {
await DoorstepAI.init({
notificationTitle: 'Tracking...',
notificationText: 'Tracking your delivery',
});
await DoorstepAI.setApiKey({ key: 'your-api-key' });
setReady(true);
})();
}, []);
return ready ? <>{children}</> : null;
}
Wrap your root layout with the provider (Next.js example):
import { DoorstepProvider } from './providers/DoorstepProvider';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<DoorstepProvider>{children}</DoorstepProvider>
</body>
</html>
);
}
Initialization parameters
DoorstepAI.init(...):
| Parameter | Type | Required | Description |
|---|---|---|---|
notificationTitle | string | Yes | Title on the foreground tracking notification |
notificationText | string | Yes | Body text on the foreground tracking notification |
DoorstepAI.setApiKey(...):
| Parameter | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Your DoorstepAI API key (note: the field is key, not apiKey) |
Manual Initialization
For more control, expose a small helper and request permissions yourself:
import { Capacitor } from '@capacitor/core';
import { DoorstepAI } from '@doorstepai/dropoff-capacitor';
export async function initializeSDK(): Promise<void> {
// Initialize SDK first
await DoorstepAI.init({
notificationTitle: 'Tracking...',
notificationText: 'Tracking your delivery',
});
// Set API key after initialization
await DoorstepAI.setApiKey({ key: 'your-api-key' });
// Request permissions on Android
if (Capacitor.getPlatform() === 'android') {
await requestAndroidPermissions();
}
}
async function requestAndroidPermissions(): Promise<boolean> {
const result = await DoorstepAI.requestPermissions({
permissions: ['location', 'activityRecognition'],
});
return Object.values(result.permissions).every((status) => status === 'granted');
}
Store your API key in environment variables exposed to the client (e.g. NEXT_PUBLIC_* in Next.js, NG_APP_* or environment.ts in Angular, VITE_* in Vite-based apps) or fetched from your backend at runtime. Never hardcode API keys in production builds.
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, type AddressType } from '@doorstepai/dropoff-capacitor';
// By Google Place ID
await DoorstepAI.startDeliveryByPlaceID({ placeID: 'some_place_id', deliveryId: 'delivery_12345' });
// By address components
const address: AddressType = {
streetNumber: '123',
route: 'Main Street',
subPremise: 'Apt 4B',
locality: 'San Francisco',
administrativeAreaLevel1: 'CA',
postalCode: '94102',
};
await DoorstepAI.startDeliveryByAddress({ address, deliveryId: 'delivery_12345' });
// By single address string, with optional coordinates and knobs
await DoorstepAI.startDeliveryByAddressString({
address: '123 Main St, Apt 4B, San Francisco, CA 94102',
deliveryId: 'delivery_12345',
coordinates: { lat: 37.7749, lng: -122.4194 }, // optional
timeoutSeconds: 1200, // optional
});
Start parameters
Every startDelivery* method takes a deliveryId plus these optional knobs:
| Parameter | Type | Description |
|---|---|---|
deliveryId | string | Required. Unique per session; correlate it on your side |
timeoutSeconds | number? | Auto-stops tracking after this duration, a backstop if the exit geofence is missed |
autoStopAfterDropoffSeconds | number? | Auto-stops this many seconds after markDropoff. Falls back to remote config if omitted |
manualForeground | boolean? | (Android only) When true, the SDK won't promote its tracking service to the foreground; your app must already run its own |
coordinates | LatLngObject? | (address / addressString variants only) pairs a textual address with a lat/lng you resolved upstream |
Deprecated start methods
startDeliveryByPlusCode and startDeliveryByLatLng are deprecated. Use startDeliveryByAddressString / startDeliveryByAddress with coordinates instead. They remain for backwards compatibility:
await DoorstepAI.startDeliveryByPlusCode({ plusCode: 'some_plus_code', deliveryId: 'delivery_12345' });
await DoorstepAI.startDeliveryByLatLng({
latitude: 37.7749,
longitude: -122.4194,
subUnit: 'Apt 4B',
deliveryId: 'delivery_12345',
});
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:
import { DoorstepAI, DropoffType } from '@doorstepai/dropoff-capacitor';
await DoorstepAI.markDropoff({
deliveryId: 'delivery_12345',
dropoffType: DropoffType.POD, // or DropoffType.NON_POD
});
markDropoff parameters
| Parameter | Type | Description |
|---|---|---|
deliveryId | string | Required. The session you're marking |
dropoffType | DropoffType | POD (proof of delivery captured) or NON_POD |
Custom Events
Use newEvent to record a custom event on a delivery:
await DoorstepAI.newEvent({ eventName: 'taking_pod', deliveryId: 'delivery_12345' });
4. Stop Tracking
Call stopDelivery when the driver exits the delivery geofence:
await DoorstepAI.stopDelivery({ deliveryId: '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 { Capacitor } from '@capacitor/core';
import { DoorstepAI } from '@doorstepai/dropoff-capacitor';
if (Capacitor.getPlatform() === 'ios') {
// Defaults to "Always" location authorization. Pass false for when-in-use.
await DoorstepAI.requestAllPermissions({ requestAlwaysLocation: true });
}
Remote Logging
Stream SDK logs to DoorstepAI for support investigations:
await DoorstepAI.configureRemoteLogging({
enabled: true,
minLevel: 'warning', // 'debug' | 'info' | 'warning' | 'error'
flushInterval: 30, // seconds
batchSize: 50,
maxQueueSize: 1000,
});
Best Practices
Lifecycle Management
The SDK keeps tracking in the background, so you usually don't need to react to app-state changes. If you want to observe them, use Capacitor's App plugin: subscribe in your mount/init hook and unsubscribe on teardown (ngOnInit/ngOnDestroy in Angular):
'use client';
import { useEffect } from 'react';
import { App } from '@capacitor/app';
export function LifecycleListener() {
useEffect(() => {
let cleanup: (() => void) | undefined;
App.addListener('appStateChange', ({ isActive }) => {
// SDK continues tracking in the background regardless of isActive
}).then((handle) => {
cleanup = () => handle.remove();
});
return () => cleanup?.();
}, []);
return null;
}
Permission Handling
Runtime permissions differ by platform:
- Android: manual runtime permission requests are required. Request them through the SDK during init (see Manual Initialization). Background location additionally requires the
ACCESS_BACKGROUND_LOCATIONpermission (API 29+). - iOS: the system prompts automatically; you can trigger the prompts up front via Requesting Permissions Up-Front (iOS). Also ensure the location and motion usage descriptions are in
Info.plist. - Wrap SDK calls in
try/catch; they reject on invalid keys, denied runtime permissions, and bad input.
Platform-Specific Notes
Framework Integration
- The plugin is native-only, so never call it during server-side rendering, pre-rendering, or build-time static generation. Guard with
Capacitor.isNativePlatform()if your code may also run on the web or during SSR. - Capacitor needs a fully static build to bundle. In Next.js, set
output: 'export'; other frameworks (Angular, Vue, plain React) already produce static output by default. - Re-run
npm run build && npx cap syncwhenever your web code changes need to ship to the native shell. - Next.js specifically: import and call the SDK only from
'use client'modules. - Angular specifically: call from component lifecycle hooks (
ngOnInitand later), not fromAPP_INITIALIZERfactories that run before the platform is ready.
Next Steps
- 💡 View Examples: complete implementation examples
- 🛠️ Troubleshooting Guide: common integration and runtime issues