Capacitor SDK Examples
Complete implementation examples and troubleshooting for the DoorstepAI Capacitor SDK.
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):
'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 syncafter 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
minSdkVersionis set to 24 or higher inandroid/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/HttpClientfrom 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.webDirshould point atdist/<project-name>/browser(Angular 17+) ordist/<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
ngOnInitor later — not during module loading. - Vue / Nuxt: call from
onMounted— not fromsetup()on the server.
- Next.js: import from a
'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_LOCATIONpermission 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)