Skip to main content

Capacitor SDK Usage & Integration

Learn how to initialize the DoorstepAI SDK and integrate delivery tracking into any Capacitor application.

Framework-Agnostic API

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.ngOnInit or an APP_INITIALIZER that returns a Promise
  • Vue / Nuxt: onMounted in your root component, or a plugin
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>
);
}
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.

Manual Initialization

For more control over the initialization process, 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<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

ParameterTypeDescriptionRequired
apiKeystringYour DoorstepAI API keyRequired
notificationTitlestringTitle for tracking notificationsRequired
notificationTextstringDescription for tracking notificationsRequired
Notification Customization

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

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 after markDropoff is called. If omitted, the SDK falls back to remote config.
  • manualForeground (Android only) — when true, 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);
}
}
Geofence Start Requirement

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

Deprecated

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

Deprecated

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,
});
  • 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);
}
}
Geofence Stop Requirement

Execute stopDelivery(...) when the driver exits the delivery geofence surrounding the building. Do not stop inside the building.

SDK Methods Reference

Authentication

MethodParametersDescription
setApiKey{ key: string }Set your DoorstepAI API key
await DoorstepAI.setApiKey({ key: 'your-api-key' });

Delivery Management

MethodParametersDescription
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.

Next.js example — app/components/LifecycleListener.tsx
'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_LOCATION permission (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 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 - See complete implementation examples
  2. 🛠️ Troubleshooting Guide - Solve common integration and runtime issues