Flutter SDK Examples
Complete implementation examples and troubleshooting for the DoorstepAI Flutter SDK.
Basic DoorstepAI SDK Test App
A complete Flutter app demonstrating all SDK features:
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// Your plugin's Dart API:
import 'package:doorstepai_dropoff_sdk/doorstepai_dropoff_sdk.dart';
// The SwiftUI‐in‐Flutter view:
import 'package:doorstepai_dropoff_sdk/doorstep_ai_view.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// Store the API key for reuse
final String _apiKey = 'your_api_key';
String? _currentDeliveryId;
// Controllers for input fields
final TextEditingController _deliveryIdController = TextEditingController();
final TextEditingController _placeIdController = TextEditingController();
final TextEditingController _plusCodeController = TextEditingController();
final TextEditingController _eventNameController = TextEditingController();
// Address component controllers
final TextEditingController _streetNumberController = TextEditingController();
final TextEditingController _routeController = TextEditingController();
final TextEditingController _subPremiseController = TextEditingController();
final TextEditingController _localityController = TextEditingController();
final TextEditingController _administrativeAreaController = TextEditingController();
final TextEditingController _postalCodeController = TextEditingController();
// Global key for ScaffoldMessenger
final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
@override
void initState() {
super.initState();
// Initialization and API key setting are handled by DoorstepAiView
// Set some default values for testing
_deliveryIdController.text = "test_delivery_${DateTime.now().millisecondsSinceEpoch}";
_placeIdController.text = 'place_id';
_plusCodeController.text = 'plus_code';
_eventNameController.text = 'test_event';
// Default address values
_streetNumberController.text = '123';
_routeController.text = 'Main Street';
_subPremiseController.text = 'Apt 4B';
_localityController.text = 'San Francisco';
_administrativeAreaController.text = 'CA';
_postalCodeController.text = '94102';
}
@override
void dispose() {
_deliveryIdController.dispose();
_placeIdController.dispose();
_plusCodeController.dispose();
_eventNameController.dispose();
_streetNumberController.dispose();
_routeController.dispose();
_subPremiseController.dispose();
_localityController.dispose();
_administrativeAreaController.dispose();
_postalCodeController.dispose();
super.dispose();
}
Future<void> _startDeliveryByPlaceID() async {
try {
final deliveryId = _deliveryIdController.text.isNotEmpty
? _deliveryIdController.text
: "delivery_${DateTime.now().millisecondsSinceEpoch}";
await DoorstepAI.startDeliveryByPlaceID(
placeID: _placeIdController.text,
deliveryId: deliveryId,
);
setState(() {
_currentDeliveryId = deliveryId;
});
_showSnackBar('Started delivery by Place ID: $deliveryId');
print('Started delivery by Place ID: $deliveryId');
} on PlatformException catch (e) {
_showSnackBar('Error: ${e.message}', isError: true);
debugPrint('Start delivery by Place ID error: ${e.message}');
}
}
Future<void> _startDeliveryByPlusCode() async {
try {
final deliveryId = _deliveryIdController.text.isNotEmpty
? _deliveryIdController.text
: "delivery_${DateTime.now().millisecondsSinceEpoch}";
await DoorstepAI.startDeliveryByPlusCode(
plusCode: _plusCodeController.text,
deliveryId: deliveryId,
);
setState(() {
_currentDeliveryId = deliveryId;
});
_showSnackBar('Started delivery by Plus Code: $deliveryId');
print('Started delivery by Plus Code: $deliveryId');
} on PlatformException catch (e) {
_showSnackBar('Error: ${e.message}', isError: true);
debugPrint('Start delivery by Plus Code error: ${e.message}');
}
}
Future<void> _startDeliveryByAddress() async {
try {
final deliveryId = _deliveryIdController.text.isNotEmpty
? _deliveryIdController.text
: "delivery_${DateTime.now().millisecondsSinceEpoch}";
final address = AddressType(
streetNumber: _streetNumberController.text,
route: _routeController.text,
subPremise: _subPremiseController.text,
locality: _localityController.text,
administrativeAreaLevel1: _administrativeAreaController.text,
postalCode: _postalCodeController.text,
);
await DoorstepAI.startDeliveryByAddress(
address: address,
deliveryId: deliveryId,
);
setState(() {
_currentDeliveryId = deliveryId;
});
_showSnackBar('Started delivery by Address: $deliveryId');
print('Started delivery by Address: $deliveryId');
} on PlatformException catch (e) {
_showSnackBar('Error: ${e.message}', isError: true);
debugPrint('Start delivery by Address error: ${e.message}');
}
}
Future<void> _sendEvent() async {
if (_currentDeliveryId == null) {
_showSnackBar('No active delivery. Start a delivery first.', isError: true);
return;
}
try {
await DoorstepAI.newEvent(
eventName: _eventNameController.text,
deliveryId: _currentDeliveryId!,
);
_showSnackBar('Event sent: ${_eventNameController.text}');
print('Event sent: ${_eventNameController.text}');
} on PlatformException catch (e) {
_showSnackBar('Error: ${e.message}', isError: true);
debugPrint('Send event error: ${e.message}');
}
}
Future<void> _stopDelivery() async {
if (_currentDeliveryId == null) {
_showSnackBar('No active delivery to stop.', isError: true);
return;
}
try {
await DoorstepAI.stopDelivery(deliveryId: _currentDeliveryId!);
_showSnackBar('Stopped delivery: $_currentDeliveryId');
print('Stopped delivery with ID: $_currentDeliveryId');
setState(() {
_currentDeliveryId = null;
});
} on PlatformException catch (e) {
_showSnackBar('Error: ${e.message}', isError: true);
debugPrint('Stop delivery error: ${e.message}');
}
}
void _showSnackBar(String message, {bool isError = false}) {
_scaffoldMessengerKey.currentState?.showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: isError ? Colors.red : Colors.green,
duration: const Duration(seconds: 3),
),
);
}
Widget _buildInputField({
required String label,
required TextEditingController controller,
String? placeholder,
TextInputType? keyboardType,
}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
),
const SizedBox(height: 4),
TextField(
controller: controller,
keyboardType: keyboardType,
decoration: InputDecoration(
hintText: placeholder,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
),
],
),
);
}
Widget _buildSection({
required String title,
required List<Widget> children,
}) {
return Card(
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
...children,
],
),
),
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
scaffoldMessengerKey: _scaffoldMessengerKey,
home: Scaffold(
appBar: AppBar(
title: const Text('doorstep.ai DropOff SDK Test App'),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
body: SingleChildScrollView(
child: Column(
children: [
// SDK Status Banner
Container(
width: double.infinity,
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.lightBlue,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'DoorstepAI SDK ready',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
// Delivery ID Section
_buildSection(
title: 'Delivery ID',
children: [
_buildInputField(
label: 'Delivery ID',
controller: _deliveryIdController,
placeholder: 'Enter Delivery ID',
),
],
),
// Start New Delivery Section
_buildSection(
title: 'Start New Delivery',
children: [
_buildInputField(
label: 'Place ID',
controller: _placeIdController,
placeholder: 'Enter Google Place ID',
),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _startDeliveryByPlaceID,
child: const Text('Start Delivery by Place ID'),
),
),
const SizedBox(height: 16),
_buildInputField(
label: 'Plus Code',
controller: _plusCodeController,
placeholder: 'Enter Plus Code',
),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _startDeliveryByPlusCode,
child: const Text('Start Delivery by Plus Code'),
),
),
],
),
// Address Components Section
_buildSection(
title: 'Address Components',
children: [
_buildInputField(
label: 'Street Number',
controller: _streetNumberController,
placeholder: 'e.g., 123',
),
_buildInputField(
label: 'Route',
controller: _routeController,
placeholder: 'e.g., Main Street',
),
_buildInputField(
label: 'Sub Premise (Apt/Suite)',
controller: _subPremiseController,
placeholder: 'e.g., Apt 4B',
),
_buildInputField(
label: 'Locality (City)',
controller: _localityController,
placeholder: 'e.g., San Francisco',
),
_buildInputField(
label: 'Administrative Area (State)',
controller: _administrativeAreaController,
placeholder: 'e.g., CA',
),
_buildInputField(
label: 'Postal Code',
controller: _postalCodeController,
placeholder: 'e.g., 94102',
keyboardType: TextInputType.number,
),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _startDeliveryByAddress,
child: const Text('Start Delivery by Address'),
),
),
],
),
// Events Section
_buildSection(
title: 'Events',
children: [
_buildInputField(
label: 'Event Name',
controller: _eventNameController,
placeholder: 'Enter event name',
),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _sendEvent,
child: const Text('Send Event'),
),
),
],
),
// Control Section
_buildSection(
title: 'Delivery Control',
children: [
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: _currentDeliveryId != null ? _stopDelivery : null,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
child: const Text('Stop Delivery'),
),
),
],
),
if (_currentDeliveryId != null) ...[
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
border: Border.all(color: Colors.green),
),
child: Text(
'Active Delivery: $_currentDeliveryId',
style: const TextStyle(
color: Colors.green,
fontWeight: FontWeight.bold,
),
),
),
],
],
),
// DoorstepAI View
Container(
margin: const EdgeInsets.all(16),
child: DoorstepAiView(
apiKey: _apiKey,
notificationTitle: "Tracking...",
notificationText: "Tracking your delivery"
),
),
const SizedBox(height: 24),
],
),
),
),
);
}
}
Troubleshooting
Common Issues
1. Initialization Errors
Problem: SDK fails to initialize
Error: PlatformException(INIT_ERROR, Failed to initialize SDK, null)
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, autolinking for Android)
2. Permission Issues
Problem: Location permissions not granted
Error: PlatformException(PERMISSION_DENIED, Location permission required, null)
Solution:
- For Android: Request runtime permissions using
permission_handler
- For iOS: Ensure usage descriptions are in
Info.plist
- Check permission status before starting delivery
import 'package:permission_handler/permission_handler.dart';
Future<bool> checkPermissions() async {
if (Platform.isAndroid) {
final locationStatus = await Permission.location.status;
final activityStatus = await Permission.activityRecognition.status;
if (!locationStatus.isGranted || !activityStatus.isGranted) {
// Request permissions
final locationResult = await Permission.location.request();
final activityResult = await Permission.activityRecognition.request();
return locationResult.isGranted && activityResult.isGranted;
}
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
pod deintegrate
pod install
cd ..
flutter clean
flutter pub get
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 21 or higher
4. Runtime Errors
Problem: Method channel errors
Error: PlatformException(METHOD_NOT_FOUND, No method found for channel doorstep_ai, null)
Solution:
- Ensure the native SDK is properly linked
- Check that the method channel name matches between Dart and native code
- Verify platform-specific implementation
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
if (Platform.isAndroid && await _getAndroidVersion() >= 29) {
final backgroundLocationStatus = await Permission.locationWhenInUse.status;
if (backgroundLocationStatus.isGranted) {
await Permission.locationAlways.request();
}
}
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 view renders correctly (iOS)
- ✅ No memory leaks in long-running sessions
- ✅ App handles lifecycle changes properly