Capacitor SDK Usage & Integration
Learn how to initialize the DoorstepAI SDK and integrate delivery tracking into any Capacitor application.
The SDK API is identical across frameworks (Angular, React, Vue, Next.js, Svelte, etc.). The code samples below are written in Next.js / TypeScript for concreteness — translate the import statements and lifecycle hooks to your framework's idioms. The DoorstepAI calls themselves never change.
SDK Initialization
Basic Setup (Run Once at App Startup)
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
'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>
);
}
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.
Manual Initialization
For more control over the initialization process, 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<void> {
const result = await DoorstepAI.requestPermissions({
permissions: ['location', 'activityRecognition'],
});
const allGranted = Object.values(result.permissions).every(
(status) => status === 'granted',
);
if (allGranted) {
console.log('✅ Required Android permissions granted');
} else {
console.log('❌ One or more required Android permissions denied');
}
}
Initialization Parameters
| Parameter | Type | Description | Required |
|---|---|---|---|
apiKey | string | Your DoorstepAI API key | ✅ Required |
notificationTitle | string | Title for tracking notifications | ✅ Required |
notificationText | string | Description for tracking notifications | ✅ Required |
The notification title and text are used for the foreground service notification that appears when the SDK is actively tracking deliveries. These parameters are required for proper SDK initialization.
Core Functionality
Delivery IDs are designed to be unique identifiers for each delivery session. Use meaningful, unique identifiers that help you track and manage your deliveries effectively.
Starting Delivery Tracking
The SDK provides multiple methods to start delivery tracking based on different address formats.
Every start method accepts these optional knobs:
timeoutSeconds— auto-stops tracking after the given duration, as a backstop in case an exit geofence is missed.autoStopAfterDropoffSeconds— auto-stops tracking the given number of seconds aftermarkDropoffis called. If omitted, the SDK falls back to remote config.manualForeground(Android only) — whentrue, the SDK does not promote its tracking service to the foreground; the host app must already be running its own foreground service.
The address and addressString variants additionally accept coordinates: LatLngObject so you can pair a textual address with the lat/lng you already resolved upstream.
1. Start by Place ID
import { DoorstepAI } from '@doorstepai/dropoff-capacitor';
export async function startDeliveryByPlaceID(): Promise<void> {
try {
await DoorstepAI.startDeliveryByPlaceID({
placeID: 'some_place_id',
deliveryId: 'delivery_12345',
timeoutSeconds: 1200, // optional
autoStopAfterDropoffSeconds: 600, // optional
manualForeground: false, // optional, Android-only
});
console.log('✅ Delivery started successfully');
} catch (error) {
console.error('❌ Failed to start delivery:', error);
}
}
Execute startDelivery... when the driver enters the delivery geofence around the target building. Do not start inside the building.
2. Start by Address Components
import { DoorstepAI, type AddressType } from '@doorstepai/dropoff-capacitor';
export async function startDeliveryByAddress(): Promise<void> {
try {
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',
coordinates: { lat: 37.7749, lng: -122.4194 }, // optional
timeoutSeconds: 1200, // optional
autoStopAfterDropoffSeconds: 600, // optional
manualForeground: false, // optional, Android-only
});
console.log('✅ Address delivery started');
} catch (error) {
console.error('❌ Address delivery failed:', error);
}
}
3. Start by Address String
export async function startDeliveryByAddressString(): Promise<void> {
try {
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
autoStopAfterDropoffSeconds: 600, // optional
manualForeground: false, // optional, Android-only
});
console.log('✅ Address string delivery started');
} catch (error) {
console.error('❌ Address string delivery failed:', error);
}
}
Deprecated: Start by Plus Code
startDeliveryByPlusCode is deprecated. Use startDeliveryByAddressString (or startDeliveryByAddress) and pass coordinates if you have them.
// Deprecated — kept for backwards compatibility.
await DoorstepAI.startDeliveryByPlusCode({
plusCode: 'some_plus_code',
deliveryId: 'delivery_12345',
timeoutSeconds: 1200,
});
Deprecated: Start by Lat/Lng
startDeliveryByLatLng is deprecated. Use startDeliveryByAddressString or startDeliveryByAddress with the coordinates parameter instead.
// Deprecated — kept for backwards compatibility.
await DoorstepAI.startDeliveryByLatLng({
latitude: 37.7749,
longitude: -122.4194,
subUnit: 'Apt 4B',
deliveryId: 'delivery_12345',
timeoutSeconds: 1200,
});
Recommended Geofence Settings
- Use both an enter and exit geofence to bound the delivery session.
- A radius of 250m or larger typically yields the best results.
Delivery Events
Track important delivery milestones using event reporting:
Mark Dropoff (POD or non-POD)
import { DoorstepAI, DropoffType } from '@doorstepai/dropoff-capacitor';
try {
await DoorstepAI.markDropoff({
deliveryId: 'delivery_12345',
dropoffType: DropoffType.POD, // or DropoffType.NON_POD
});
console.log('✅ Dropoff marked');
} catch (error) {
console.error('❌ Dropoff mark failed:', error);
}
// NOTE: The previous methods of marking a dropoff still work as expected.
try {
await DoorstepAI.newEvent({
eventName: 'taking_pod',
deliveryId: 'delivery_12345',
});
console.log('Event sent');
} catch (error) {
console.error('Event send failed:', error);
}
Stop Delivery Tracking
export async function stopDelivery(deliveryId: string): Promise<void> {
try {
await DoorstepAI.stopDelivery({ deliveryId });
console.log('✅ Delivery stopped successfully');
} catch (error) {
console.error('❌ Failed to stop delivery:', error);
}
}
Execute stopDelivery(...) when the driver exits the delivery geofence surrounding the building. Do not stop inside the building.
SDK Methods Reference
Authentication
| Method | Parameters | Description |
|---|---|---|
setApiKey | { key: string } | Set your DoorstepAI API key |
await DoorstepAI.setApiKey({ key: 'your-api-key' });
Delivery Management
| Method | Parameters | Description |
|---|---|---|
startDeliveryByPlaceID | { placeID, deliveryId, timeoutSeconds? } | Start delivery tracking using Google Place ID |
startDeliveryByPlusCode | { plusCode, deliveryId, timeoutSeconds? } | Start delivery tracking using Plus Code |
startDeliveryByAddress | { address: AddressType, deliveryId, timeoutSeconds? } | Start delivery tracking using address components |
startDeliveryByAddressString | { address: string, deliveryId, timeoutSeconds? } | Start delivery tracking using a single address string |
startDeliveryByLatLng | { latitude, longitude, subUnit?, deliveryId, timeoutSeconds? } | Start delivery tracking using latitude/longitude |
markDropoff | { deliveryId, dropoffType: DropoffType } | Mark a POD or non-POD dropoff |
newEvent | { eventName, deliveryId } | Send custom event for active delivery |
stopDelivery | { deliveryId } | Stop delivery tracking |
Address Type
export interface AddressType {
streetNumber: string;
route: string;
subPremise?: string;
locality: string;
administrativeAreaLevel1: string;
postalCode: string;
}
LatLngObject
Pair a textual address with lat/lng you already resolved upstream by passing it as coordinates on startDeliveryByAddress or startDeliveryByAddressString.
export interface LatLngObject {
lat: number;
lng: number;
}
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 });
}
Retrying GNSS After Permission Grants (Android)
If the user grants location permissions after SDK init, retry GNSS callback registration so raw GNSS data starts flowing:
if (Capacitor.getPlatform() === 'android') {
await DoorstepAI.retryGnssCallbacks();
}
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,
});
Dev Mode
Validate the API token and enable dev mode if it's authorized:
const { enabled } = await DoorstepAI.enableDevMode({ apiKey: 'YOUR_API_KEY' });
if (enabled) {
console.log('✅ Dev mode active');
}
Best Practices
1. Error Handling
Always implement proper error handling for SDK methods:
export async function startDeliveryWithErrorHandling(): Promise<void> {
try {
await DoorstepAI.startDeliveryByPlaceID({
placeID: 'your-place-id',
deliveryId: 'unique-delivery-id',
});
// Handle success
} catch (error) {
// Handle error
console.error('Error:', error);
}
}
2. Lifecycle Management
Handle app lifecycle changes using Capacitor's App plugin. In any framework, subscribe in your component's mount/init hook and unsubscribe on teardown. The Next.js version is shown below; the Angular equivalent registers the listener in ngOnInit and removes it in ngOnDestroy.
'use client';
import { useEffect } from 'react';
import { App, type AppState } from '@capacitor/app';
export function LifecycleListener() {
useEffect(() => {
let cleanup: (() => void) | undefined;
(async () => {
const handle = await App.addListener('appStateChange', (state: AppState) => {
if (state.isActive) {
console.log('📱 App resumed');
} else {
console.log('📱 App paused - SDK continues tracking in background');
}
});
cleanup = () => handle.remove();
})();
return () => cleanup?.();
}, []);
return null;
}
3. Permission Handling
You can request permissions through the SDK or via the Capacitor permissions APIs:
import { Capacitor } from '@capacitor/core';
import { DoorstepAI } from '@doorstepai/dropoff-capacitor';
export async function requestPermissions(): Promise<void> {
if (Capacitor.getPlatform() === 'android') {
const result = await DoorstepAI.requestPermissions({
permissions: ['location', 'activityRecognition'],
});
const allGranted = Object.values(result.permissions).every(
(status) => status === 'granted',
);
if (allGranted) {
console.log('✅ Required Android permissions granted');
} else {
console.log('❌ One or more required Android permissions denied');
}
} else if (Capacitor.getPlatform() === 'ios') {
console.log('📱 iOS: Ensure location and motion usage descriptions are in Info.plist');
}
}
Platform-Specific Notes
iOS
- Permissions are handled automatically by the system, although requesting them in advance is sometimes recommended.
Android
- Manual permission requests are required
- Background location requires
ACCESS_BACKGROUND_LOCATIONpermission (API 29+)
Framework Integration
- The plugin is native-only — 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 - See complete implementation examples
- 🛠️ Troubleshooting Guide - Solve common integration and runtime issues