Skip to main content

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 DoorstepAI calls are identical across frameworks; translate only the imports and lifecycle hooks. See Installation for the framework-agnostic details.

#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
Runtime Permissions

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.ngOnInit or an APP_INITIALIZER that returns a Promise
  • Vue / Nuxt: onMounted in 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.

Next.js example: app/providers/DoorstepProvider.tsx
'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):

app/layout.tsx
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(...):

ParameterTypeRequiredDescription
notificationTitlestringYesTitle on the foreground tracking notification
notificationTextstringYesBody text on the foreground tracking notification

DoorstepAI.setApiKey(...):

ParameterTypeRequiredDescription
keystringYesYour DoorstepAI API key (note: the field is key, not apiKey)

Manual Initialization

For more control, expose a small helper and request permissions yourself:

Example: lib/doorstep.ts
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');
}
API Key Security

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:

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
autoStopAfterDropoffSecondsnumber?Auto-stops this many seconds after markDropoff. Falls back to remote config if omitted
manualForegroundboolean?(Android only) When true, the SDK won't promote its tracking service to the foreground; your app must already run its own
coordinatesLatLngObject?(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

ParameterTypeDescription
deliveryIdstringRequired. The session you're marking
dropoffTypeDropoffTypePOD (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):

Next.js example: app/components/LifecycleListener.tsx
'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_LOCATION permission (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 sync whenever 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 (ngOnInit and later), not from APP_INITIALIZER factories that run before the platform is ready.

Next Steps

  1. 💡 View Examples: complete implementation examples
  2. 🛠️ Troubleshooting Guide: common integration and runtime issues