Skip to main content

Flutter SDK Examples

Quickstart

This example uses the current 2.2.2 typed permission flow and the supported address-string start.

import 'package:doorstepai_dropoff_sdk/doorstepai_dropoff_sdk.dart';

Future<void> startDelivery() async {
await DoorstepAI.init(
notificationTitle: 'Tracking...',
notificationText: 'Tracking your delivery',
);
await DoorstepAI.setApiKey('your-api-key');

// Safe no-op when no route was persisted.
await DoorstepAI.resumeRouteGeofencingIfNeeded();

final foreground = await DoorstepAI.requestPermissions(
const PermissionRequestOptions(
permissions: [
DoorstepPermissionType.locationWhenInUse,
DoorstepPermissionType.motion,
DoorstepPermissionType.bluetooth,
DoorstepPermissionType.nearbyWifiDevices,
DoorstepPermissionType.notifications,
],
),
);

if (foreground.statuses[DoorstepPermissionType.locationWhenInUse]?.state ==
DoorstepPermissionState.granted) {
await DoorstepAI.requestBackgroundLocationPermission();
}

final serverSessionId = await DoorstepAI.startDeliveryByAddressString(
address: '123 Main St, Apt 4B, San Francisco, CA 94102',
deliveryId: 'delivery_12345',
coordinates: const LatLngObject(lat: 37.7749, lng: -122.4194),
timeoutSeconds: 1800,
customerId: 'customer_42',
driverId: 'driver_7',
);
// Android returns the server id here. It is null on iOS; use the event stream.
print(serverSessionId);

await DoorstepAI.newEvent(
eventName: 'taking_pod',
deliveryId: 'delivery_12345',
);
await DoorstepAI.markDropoff(
deliveryId: 'delivery_12345',
dropoffType: DropoffType.pod,
);
await DoorstepAI.stopDelivery(deliveryId: 'delivery_12345');
}

Call the permission prompts from user-visible onboarding or a Start shift action. In production, do not request them automatically during a cold launch unless that timing is deliberate.

Route-managed lifecycle

Use this flow when the SDK should own geofence registration and automatic session starts/stops:

import 'dart:async';

import 'package:doorstepai_dropoff_sdk/doorstepai_dropoff_sdk.dart';

late final StreamSubscription<GeofenceSessionEvent> routeEvents;
late final StreamSubscription<SessionServerIdAssignedEvent> sessionIds;

Future<void> startRoute() async {
routeEvents = DoorstepAI.geofenceSessionEvents.listen((event) {
// Android STARTED events can include serverSessionId.
print('${event.deliveryId} ${event.type} ${event.reason} '
'${event.serverSessionId}');
});

sessionIds = DoorstepAI.sessionServerIdAssignedEvents.listen((event) {
// iOS start methods publish the server id asynchronously.
print('${event.deliveryId} ${event.serverSessionId}');
});

await DoorstepAI.startRouteGeofencing(
const [
DeliveryStop(
deliveryId: 'delivery_12345',
address: '123 Main St, San Francisco, CA',
latitude: 37.7749,
longitude: -122.4194,
customerId: 'customer_42',
driverId: 'driver_7',
),
DeliveryStop(
deliveryId: 'delivery_67890',
address: '500 Market St, San Francisco, CA',
latitude: 37.7899,
longitude: -122.4009,
radiusMeters: 200,
),
],
options: const RouteGeofenceOptions(
defaultRadiusMeters: 250,
timeoutSeconds: 1800,
manualForeground: false, // Android only
),
);
}

Future<void> updateRoute() async {
await DoorstepAI.updateRouteStops(
const [
DeliveryStop(
deliveryId: 'delivery_12345',
address: '123 Main St, San Francisco, CA',
latitude: 37.7749,
longitude: -122.4194,
),
DeliveryStop(
deliveryId: 'delivery_99999',
address: '1 Market St, San Francisco, CA',
latitude: 37.7936,
longitude: -122.3965,
),
],
);
}

Future<void> stopRoute() async {
await DoorstepAI.stopRouteGeofencing();
await routeEvents.cancel();
await sessionIds.cancel();
}

Call markDropoff when the driver completes a stop; only then may a later fence EXIT stop that route session. Call resumeRouteGeofencingIfNeeded() after API-key setup on every launch. Background route events require locationAlways == granted.

Permission-state handling

Use detailed states to distinguish a user choice from a host-configuration problem:

final current = await DoorstepAI.checkPermissions();
final always =
current.statuses[DoorstepPermissionType.locationAlways]?.state;

switch (always) {
case DoorstepPermissionState.granted:
break;
case DoorstepPermissionState.whenInUseOnly:
// Explain why background tracking is needed, then request phase two.
await DoorstepAI.requestBackgroundLocationPermission();
break;
case DoorstepPermissionState.permanentlyDenied:
case DoorstepPermissionState.restricted:
// Direct the driver to system Settings or explain the device restriction.
break;
case DoorstepPermissionState.notDeclared:
// Fix AndroidManifest.xml or Info.plist, then rebuild the app.
break;
default:
break;
}

The request result also reports requested, alreadyDetermined, unavailable, and deferred. See Flutter Permissions for all six buckets, all eight states, and the staged flow.

Troubleshooting

SDK initialization fails

  • Await DoorstepAI.init() before setApiKey() and the first Android start.
  • Confirm the API key belongs to the intended environment.
  • Catch DoorstepAIException and log its code, message, and optional details.

A permission is unavailable or undeclared

  • unavailable means the OS version, platform, or hardware does not expose that bucket. Android-only Wi-Fi and notification buckets are expected to be unavailable on iOS.
  • notDeclared means the host build is missing an Android manifest declaration or iOS usage-description key. Fix and rebuild the host; prompting again cannot solve it.

Background tracking or route events stop

Check statuses[DoorstepPermissionType.locationAlways]?.state; whenInUseOnly is not a background grant. On Android, also verify the tracking foreground-service notification is present. Set manualForeground: true only when the host app already owns a suitable foreground service.

The iOS server session id is null

That is expected from manual starts. Subscribe to DoorstepAI.sessionServerIdAssignedEvents before starting the delivery. Android manual starts return the id directly; Android route STARTED events can include it on GeofenceSessionEvent.serverSessionId.

An Android-only permission helper fails on iOS

getRequiredPermissions, hasRequiredPermissions, and hasBackgroundLocationPermission have no iOS native counterpart and throw there. Use checkPermissions() for portable app logic.

Build or linkage failures

  • iOS: confirm the deployment target is iOS 15+, then run cd ios && pod install.
  • Android: confirm the host uses API 21+ and inspect the merged manifest.
  • Run flutter clean && flutter pub get after changing plugin or native dependencies.

Release checklist

  • SDK initialization and API-key setup succeed on both platforms
  • Foreground and background permission phases are tested on physical devices
  • Manual delivery start, event, mark, and stop calls succeed
  • Route state resumes after process relaunch
  • Both route-event and iOS session-id subscriptions are cancelled during teardown
  • Native production builds include every requested permission declaration