Skip to main content

Capacitor SDK Examples

Complete implementation examples and troubleshooting for the DoorstepAI Capacitor SDK.

Framework-Agnostic

The example below is written as a Next.js Client Component for concreteness, but every DoorstepAI.* call is identical in Angular, React, Vue, and other Capacitor-supported frameworks. Translate the React hooks (useState, useEffect) to your framework's state and lifecycle primitives (e.g. component properties + ngOnInit in Angular).

Basic DoorstepAI SDK Test App

A complete component demonstrating all SDK features (Next.js):

Next.js example — app/dropoff-test/page.tsx
'use client';

import { useEffect, useState } from 'react';
import { Capacitor } from '@capacitor/core';
import {
DoorstepAI,
type AddressType,
} from '@doorstepai/dropoff-capacitor';

const API_KEY = process.env.NEXT_PUBLIC_DOORSTEP_API_KEY ?? 'your_api_key';

export default function DropoffTestPage() {
const [currentDeliveryId, setCurrentDeliveryId] = useState<string | null>(null);
const [status, setStatus] = useState<{ message: string; isError: boolean } | null>(
null,
);

// Default delivery ID for testing
const [deliveryId, setDeliveryId] = useState(`test_delivery_${Date.now()}`);
const [placeId, setPlaceId] = useState('place_id');
const [plusCode, setPlusCode] = useState('plus_code');
const [eventName, setEventName] = useState('test_event');

// Address component fields
const [streetNumber, setStreetNumber] = useState('123');
const [route, setRoute] = useState('Main Street');
const [subPremise, setSubPremise] = useState('Apt 4B');
const [locality, setLocality] = useState('San Francisco');
const [administrativeArea, setAdministrativeArea] = useState('CA');
const [postalCode, setPostalCode] = useState('94102');

useEffect(() => {
if (!Capacitor.isNativePlatform()) {
showStatus('Web preview — DoorstepAI runs on native only');
return;
}

(async () => {
try {
await DoorstepAI.init({
notificationTitle: 'Tracking...',
notificationText: 'Tracking your delivery',
});
await DoorstepAI.setApiKey({ key: API_KEY });
showStatus('DoorstepAI SDK ready');
} catch (error) {
showStatus(`Init error: ${errorMessage(error)}`, true);
}
})();
}, []);

function resolveDeliveryId(): string {
return deliveryId.length > 0 ? deliveryId : `delivery_${Date.now()}`;
}

function showStatus(message: string, isError = false): void {
setStatus({ message, isError });
setTimeout(() => setStatus(null), 3000);
}

function errorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}

async function startDeliveryByPlaceID(): Promise<void> {
try {
const id = resolveDeliveryId();
await DoorstepAI.startDeliveryByPlaceID({
placeID: placeId,
deliveryId: id,
});
setCurrentDeliveryId(id);
showStatus(`Started delivery by Place ID: ${id}`);
} catch (error) {
showStatus(`Error: ${errorMessage(error)}`, true);
console.error('Start delivery by Place ID error:', error);
}
}

async function startDeliveryByPlusCode(): Promise<void> {
try {
const id = resolveDeliveryId();
await DoorstepAI.startDeliveryByPlusCode({
plusCode,
deliveryId: id,
});
setCurrentDeliveryId(id);
showStatus(`Started delivery by Plus Code: ${id}`);
} catch (error) {
showStatus(`Error: ${errorMessage(error)}`, true);
console.error('Start delivery by Plus Code error:', error);
}
}

async function startDeliveryByAddress(): Promise<void> {
try {
const id = resolveDeliveryId();
const address: AddressType = {
streetNumber,
route,
subPremise,
locality,
administrativeAreaLevel1: administrativeArea,
postalCode,
};

await DoorstepAI.startDeliveryByAddress({ address, deliveryId: id });
setCurrentDeliveryId(id);
showStatus(`Started delivery by Address: ${id}`);
} catch (error) {
showStatus(`Error: ${errorMessage(error)}`, true);
console.error('Start delivery by Address error:', error);
}
}

async function sendEvent(): Promise<void> {
if (!currentDeliveryId) {
showStatus('No active delivery. Start a delivery first.', true);
return;
}
try {
await DoorstepAI.newEvent({ eventName, deliveryId: currentDeliveryId });
showStatus(`Event sent: ${eventName}`);
} catch (error) {
showStatus(`Error: ${errorMessage(error)}`, true);
console.error('Send event error:', error);
}
}

async function stopDelivery(): Promise<void> {
if (!currentDeliveryId) {
showStatus('No active delivery to stop.', true);
return;
}
try {
await DoorstepAI.stopDelivery({ deliveryId: currentDeliveryId });
showStatus(`Stopped delivery: ${currentDeliveryId}`);
setCurrentDeliveryId(null);
} catch (error) {
showStatus(`Error: ${errorMessage(error)}`, true);
console.error('Stop delivery error:', error);
}
}

return (
<main className="container">
<header className="app-bar">
<h1>doorstep.ai DropOff SDK Test App</h1>
</header>

<div className="status-banner">DoorstepAI SDK ready</div>

<section className="card">
<h2>Delivery ID</h2>
<label>Delivery ID</label>
<input
value={deliveryId}
onChange={(e) => setDeliveryId(e.target.value)}
placeholder="Enter Delivery ID"
/>
</section>

<section className="card">
<h2>Start New Delivery</h2>

<label>Place ID</label>
<input
value={placeId}
onChange={(e) => setPlaceId(e.target.value)}
placeholder="Enter Google Place ID"
/>
<button onClick={startDeliveryByPlaceID}>Start Delivery by Place ID</button>

<label>Plus Code</label>
<input
value={plusCode}
onChange={(e) => setPlusCode(e.target.value)}
placeholder="Enter Plus Code"
/>
<button onClick={startDeliveryByPlusCode}>Start Delivery by Plus Code</button>
</section>

<section className="card">
<h2>Address Components</h2>

<label>Street Number</label>
<input value={streetNumber} onChange={(e) => setStreetNumber(e.target.value)} />

<label>Route</label>
<input value={route} onChange={(e) => setRoute(e.target.value)} />

<label>Sub Premise (Apt/Suite)</label>
<input value={subPremise} onChange={(e) => setSubPremise(e.target.value)} />

<label>Locality (City)</label>
<input value={locality} onChange={(e) => setLocality(e.target.value)} />

<label>Administrative Area (State)</label>
<input
value={administrativeArea}
onChange={(e) => setAdministrativeArea(e.target.value)}
/>

<label>Postal Code</label>
<input
value={postalCode}
onChange={(e) => setPostalCode(e.target.value)}
inputMode="numeric"
/>

<button onClick={startDeliveryByAddress}>Start Delivery by Address</button>
</section>

<section className="card">
<h2>Events</h2>
<label>Event Name</label>
<input
value={eventName}
onChange={(e) => setEventName(e.target.value)}
placeholder="Enter event name"
/>
<button onClick={sendEvent}>Send Event</button>
</section>

<section className="card">
<h2>Delivery Control</h2>
<button
className="danger"
disabled={!currentDeliveryId}
onClick={stopDelivery}
>
Stop Delivery
</button>

{currentDeliveryId && (
<div className="active-delivery">
Active Delivery: {currentDeliveryId}
</div>
)}
</section>

{status && (
<div className={`snackbar ${status.isError ? 'error' : ''}`}>
{status.message}
</div>
)}
</main>
);
}

Troubleshooting

Common Issues

1. Initialization Errors

Problem: SDK fails to initialize

Error: DoorstepAI: INIT_ERROR — Failed to initialize SDK

Solution:

  • Ensure all required permissions are declared in platform-specific files
  • Check that the API key is valid
  • Verify platform-specific setup (CocoaPods for iOS, plugin sync for Android)
  • Run npm run build && npx cap sync after every install or upgrade

2. Permission Issues

Problem: Location permissions not granted

Error: DoorstepAI: PERMISSION_DENIED — Location permission required

Solution:

  • For Android: Request runtime permissions through DoorstepAI.requestPermissions(...) or the platform APIs
  • For iOS: Ensure usage descriptions are in Info.plist
  • Check permission status before starting delivery
import { Capacitor } from '@capacitor/core';
import { DoorstepAI } from '@doorstepai/dropoff-capacitor';

export async function checkPermissions(): Promise<boolean> {
if (Capacitor.getPlatform() === 'android') {
const status = await DoorstepAI.checkPermissions();
const locationGranted = status.permissions.location === 'granted';
const activityGranted = status.permissions.activityRecognition === 'granted';

if (!locationGranted || !activityGranted) {
const result = await DoorstepAI.requestPermissions({
permissions: ['location', 'activityRecognition'],
});

return (
result.permissions.location === 'granted' &&
result.permissions.activityRecognition === 'granted'
);
}
return true;
}
return true; // iOS handles automatically
}

3. Build Errors

Problem: iOS build fails with CocoaPods errors

Error: CocoaPods could not find compatible versions for pod "DoorstepDropoffSDK"

Solution:

cd ios/App
pod deintegrate
pod install
cd ../..
npx cap sync ios

Problem: Android build fails with missing permissions

Error: Missing required permission: ACCESS_FINE_LOCATION

Solution:

  • Add all required permissions to AndroidManifest.xml
  • Ensure minSdkVersion is set to 24 or higher in android/variables.gradle

Problem: Framework build fails to produce static output

Error: Server Actions cannot be used with `output: 'export'`   # Next.js
Error: SSR routes detected in static build # Nuxt/SvelteKit

Solution:

  • Capacitor requires a fully static build — remove server-rendered routes, Server Actions, or dynamic API handlers from pages bundled into the native shell.
  • Move server-side logic to a separately hosted API and call it via fetch / HttpClient from the client.
  • Next.js: set output: 'export' and remove Server Actions / dynamic Route Handlers.
  • Angular: use the standard browser build (ng build) — not @angular/ssr. webDir should point at dist/<project-name>/browser (Angular 17+) or dist/<project-name> (Angular ≤16).

4. Runtime Errors

Problem: Plugin not implemented errors

Error: "DoorstepAI" plugin is not implemented on web

Solution:

  • The SDK is native-only — guard calls with Capacitor.isNativePlatform().
  • Ensure the native SDK is properly synced via npx cap sync.
  • Make sure you're calling the plugin from code that runs in the WebView after Capacitor bootstraps:
    • Next.js: import from a 'use client' module — not a Server Component or Route Handler.
    • Angular: call from a component's ngOnInit or later — not during module loading.
    • Vue / Nuxt: call from onMounted — not from setup() on the server.
Next.js example
'use client';

import { useEffect } from 'react';
import { Capacitor } from '@capacitor/core';
import { DoorstepAI } from '@doorstepai/dropoff-capacitor';

export function InitDoorstep() {
useEffect(() => {
if (!Capacitor.isNativePlatform()) return;
DoorstepAI.init({
notificationTitle: 'Tracking...',
notificationText: 'Tracking your delivery',
});
}, []);

return null;
}

5. Background Location Issues

Problem: Background location not working on Android

Error: Background location permission required for API 29+

Solution:

  • Request ACCESS_BACKGROUND_LOCATION permission for Android 10+ (API 29+)
  • Ensure the app has proper background processing capabilities
import { Capacitor } from '@capacitor/core';
import { Device } from '@capacitor/device';
import { DoorstepAI } from '@doorstepai/dropoff-capacitor';

export async function ensureBackgroundLocation(): Promise<void> {
if (Capacitor.getPlatform() !== 'android') return;

const info = await Device.getInfo();
const apiLevel = Number(info.androidSDKVersion ?? 0);

if (apiLevel >= 29) {
await DoorstepAI.requestPermissions({
permissions: ['backgroundLocation'],
});
}
}

Testing Checklist

Before deploying your app, verify:

  • ✅ SDK initializes without errors
  • ✅ API key is valid and working
  • ✅ All required permissions are granted
  • ✅ Delivery tracking starts successfully
  • ✅ Events are sent without errors
  • ✅ Delivery can be stopped properly
  • ✅ Background location works (Android)
  • ✅ Native plugin loads correctly (iOS)
  • ✅ No memory leaks in long-running sessions
  • ✅ App handles lifecycle changes properly
  • ✅ Your framework's production build produces a valid fully-static output (no SSR routes, Server Actions, or dynamic handlers in the native shell)