Flutter SDK Usage & Integration
The DoorstepAI SDK has four touchpoints in your driver app. Wire up each one and you're done. Everything else on this page is reference detail for those four calls.
| # | When this happens in the driver app | What you call |
|---|---|---|
| 1 | App launches | Initialize SDK |
| 2 | Driver enters the ≥250 m delivery geofence | Start Tracking |
| 3 | Driver takes POD or confirms drop-off in-app | Mark Drop-off |
| 4 | Driver exits the ≥250 m delivery geofence | Stop Tracking |
Tracking needs runtime location permission on Android. Request it during init (see Manual Initialization and Permission Handling). On iOS the system prompts automatically.
1. Initialize SDK
Initialize the SDK once, early in your app's lifecycle. You can let the DoorstepAiView widget handle it, or initialize manually for more control.
Option A: DoorstepAiView widget
The widget initializes the SDK and requests permissions automatically. It is no longer required as of the latest release (manual init works just as well), but it's the quickest path:
import 'package:flutter/material.dart';
import 'package:doorstepai_dropoff_sdk/doorstepai_dropoff_sdk.dart';
import 'package:doorstepai_dropoff_sdk/doorstep_ai_view.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: DoorstepAiView(
apiKey: 'your-api-key',
notificationTitle: 'Tracking...',
notificationText: 'Tracking your delivery',
),
),
);
}
}
Option B: Manual initialization
For more control, initialize the SDK and set the API key yourself, then request permissions:
import 'package:flutter/foundation.dart';
import 'package:doorstepai_dropoff_sdk/doorstepai_dropoff_sdk.dart';
class DeliveryManager {
static Future<void> initializeSDK() async {
// Initialize SDK first
await DoorstepAI.init(
notificationTitle: 'Tracking...',
notificationText: 'Tracking your delivery',
);
// Set API key after initialization
await DoorstepAI.setApiKey('your-api-key');
// Request permissions on Android (see requestPermissions() below)
if (defaultTargetPlatform == TargetPlatform.android) {
await requestPermissions();
}
}
}
See Permission Handling below for the requestPermissions() implementation.
Initialization parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
apiKey | String | Yes | Your DoorstepAI API key |
notificationTitle | String? | No | Title on the foreground-service notification shown while tracking |
notificationText | String? | No | Description on that notification |
Store your API key securely using environment variables, secure storage, or a configuration service. Never hardcode API keys in production builds.
2. Start Tracking
Call startDelivery… when the driver enters the delivery geofence around the target building, not once inside. Use a unique deliveryId per session: a meaningful identifier you can correlate on your side. Wrap calls in try/catch to handle failures such as an invalid key or denied permissions.
Pick whichever address format you have (startDeliveryByPlaceID, startDeliveryByAddress, or startDeliveryByAddressString):
import 'package:doorstepai_dropoff_sdk/doorstepai_dropoff_sdk.dart';
// By Google Place ID
await DoorstepAI.startDeliveryByPlaceID(
placeID: 'some_place_id',
deliveryId: 'delivery_12345',
);
// By address components
final address = AddressType(
streetNumber: '123',
route: 'Main Street',
subPremise: 'Apt 4B',
locality: 'San Francisco',
administrativeAreaLevel1: 'CA',
postalCode: '94102',
);
await DoorstepAI.startDeliveryByAddress(
address: address,
deliveryId: 'delivery_12345',
);
// By single address string, with optional coordinates and knobs
await DoorstepAI.startDeliveryByAddressString(
address: '123 Main St, Apt 4B, San Francisco, CA 94102',
deliveryId: 'delivery_12345',
coordinates: LatLngObject(lat: 37.7749, lng: -122.4194),
timeoutSeconds: 1200,
);
Start parameters
Every startDelivery* method takes a deliveryId plus these optional knobs:
| Parameter | Type | Description |
|---|---|---|
deliveryId | String | Required. Unique per session; correlate it on your side |
timeoutSeconds | int? | Auto-stops tracking after this duration, a backstop if the exit geofence is missed |
autoStopAfterDropoffSeconds | int? | Auto-stops this many seconds after markDropoff. Falls back to remote config if omitted |
manualForeground | bool? | (Android only) When true, the SDK won't promote its tracking service to the foreground; your app must already run its own |
coordinates | LatLngObject? | (address / addressString variants only) pairs a textual address with a lat/lng you resolved upstream |
Deprecated start methods
startDeliveryByPlusCode and startDeliveryByLatLng are deprecated. Use startDeliveryByAddressString / startDeliveryByAddress with coordinates instead. They remain for backwards compatibility:
await DoorstepAI.startDeliveryByPlusCode(
plusCode: 'some_plus_code',
deliveryId: 'delivery_12345',
);
await DoorstepAI.startDeliveryByLatLng(
latitude: 37.7749,
longitude: -122.4194,
subUnit: 'Apt 4B',
deliveryId: 'delivery_12345',
);
3. Mark Drop-off
When the driver completes the delivery (takes a POD or confirms drop-off in-app), mark it. markDropoff is the preferred API:
await DoorstepAI.markDropoff(
deliveryId: 'delivery_12345',
dropoffType: DropoffType.pod, // or DropoffType.nonPod
);
markDropoff parameters
| Parameter | Type | Description |
|---|---|---|
deliveryId | String | Required. The session you're marking |
dropoffType | DropoffType | pod (proof of delivery captured) or nonPod |
Custom Events
Use newEvent to record a custom event on a delivery:
await DoorstepAI.newEvent(
eventName: 'taking_pod',
deliveryId: 'delivery_12345',
);
4. Stop Tracking
Call stopDelivery when the driver exits the delivery geofence:
await DoorstepAI.stopDelivery(deliveryId: 'delivery_12345');
That covers the full lifecycle. The sections below are reference for the calls above.
DoorstepAiView Widget
See Option A: DoorstepAiView widget above.
Advanced APIs
Requesting Permissions Up-Front (iOS)
Trigger the iOS Motion/Fitness + Location permission prompts before the first delivery:
import 'package:flutter/foundation.dart';
if (defaultTargetPlatform == TargetPlatform.iOS) {
// Defaults to "Always" location authorization. Pass false for when-in-use.
await DoorstepAI.requestAllPermissions(requestAlwaysLocation: true);
}
Remote Logging
Stream SDK logs to DoorstepAI for support investigations:
await DoorstepAI.configureRemoteLogging(
enabled: true,
minLevel: SDKLogLevel.warning, // debug | info | warning | error
flushInterval: 30, // seconds
batchSize: 50,
maxQueueSize: 1000,
);
Best Practices
Error Handling
Always implement proper error handling for SDK methods:
Future<void> startDeliveryWithErrorHandling() async {
try {
await DoorstepAI.startDeliveryByPlaceID(
placeID: 'your-place-id',
deliveryId: 'unique-delivery-id',
);
// Handle success
} catch (error) {
// Handle error
print('Error: $error');
}
}
Lifecycle Management
The SDK keeps tracking in the background, so you usually don't need to react to app-state changes. If you want to observe them, use a WidgetsBindingObserver:
import 'package:flutter/material.dart';
class DeliveryManager extends StatefulWidget {
@override
_DeliveryManagerState createState() => _DeliveryManagerState();
}
class _DeliveryManagerState extends State<DeliveryManager> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.paused:
print('App paused - SDK continues tracking in background');
break;
case AppLifecycleState.resumed:
print('App resumed');
break;
default:
break;
}
}
}
Permission Handling
The DoorstepAiView widget handles permission requests automatically, but you can also handle them manually:
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter/foundation.dart';
Future<void> requestPermissions() async {
if (defaultTargetPlatform == TargetPlatform.android) {
final permissions = {
Permission.location: 'Location',
Permission.activityRecognition: 'Activity Recognition',
};
// Request permissions sequentially
Map<Permission, PermissionStatus> results = {};
for (var entry in permissions.entries) {
final permission = entry.key;
final name = entry.value;
final status = await permission.request();
results[permission] = status;
}
if (results.values.every((status) => status.isGranted)) {
print('Required Android permissions granted');
} else {
print('One or more required Android permissions denied');
}
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
print('iOS: Ensure location and motion usage descriptions are in Info.plist');
}
}
Note: background location requires the ACCESS_BACKGROUND_LOCATION permission on Android 10+ (API 29+).
Next Steps
- 💡 View Examples: complete implementation examples
- 🛠️ Troubleshooting Guide: common integration and runtime issues