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()beforesetApiKey()and the first Android start. - Confirm the API key belongs to the intended environment.
- Catch
DoorstepAIExceptionand log itscode,message, and optionaldetails.
A permission is unavailable or undeclared
unavailablemeans 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.notDeclaredmeans 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 getafter 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