🧭 Route Progress Updates
This document explains how to implement routes that automatically update their progress as the user moves through them, providing a professional navigation experience with real-time progress indication.
🚨 Common Problem
Many developers find that routes created with addRoute() maintain static progress without showing the user’s visual progress. This happens because the location provider is not configured after creating the route.
✅ Complete Solution
1. Basic Setup
class NavigationExample extends StatefulWidget {
@override
_NavigationExampleState createState() => _NavigationExampleState();
}
class _NavigationExampleState extends State<NavigationExample> {
final LazarilloMaps _lazarilloSDK = LazarilloMaps('YOUR_API_KEY');
LazarilloMapWidget? _mapWidget;
LzRoute? _currentRoute;
Position? _currentPosition;
// Reactive notifier for current route step
final ValueNotifier<int?> _currentStepNotifier = ValueNotifier<int?>(null);
@override
void initState() {
super.initState();
_initializeSDK();
}
@override
void dispose() {
_currentStepNotifier.dispose();
super.dispose();
}
}2. SDK and Map Initialization
Future<void> _initializeSDK() async {
try {
// Initialize SDK
await _lazarilloSDK.initialize();
// Configure map
final mapConfiguration = MapConfiguration(
latitude: 42.334280097207994,
longitude: -83.04650441260361,
zoom: 15,
showFloorSelector: false,
showZoomIn: false,
showZoomOut: false,
showZoomToLocation: true,
);
// Create map widget
_mapWidget = _lazarilloSDK.getLazarilloMapWidget(
mapConfiguration,
(String mapId) {
print('Map ready: $mapId');
setState(() {
// Map is ready
});
},
);
// Start basic location tracking
_initializeLocationTracking();
} catch (e) {
print('Error initializing SDK: $e');
}
}
Future<void> _initializeLocationTracking() async {
try {
_lazarilloSDK.startUpdatingLocation((Position position) {
setState(() {
_currentPosition = position;
});
print('Location update: ${position.location?.latitude}, ${position.location?.longitude}');
});
} catch (e) {
print('Error starting location tracking: $e');
}
}3. Route Creation with Progress Updates
Future<void> navigateToPlace(LzPlace destination) async {
if (_mapWidget == null || _currentPosition?.location == null) {
print('Cannot navigate: map not ready or location not available');
return;
}
print('Creating route to ${destination.title}');
// 🎨 CONFIGURE ROUTE COLORS
// These colors will be used to show route progress:
// - nextStepsRouteColor: Color for upcoming route segments (future steps)
// - prevStepsRouteColor: Color for completed route segments (past steps)
//
// IMPORTANT: Make sure these colors are different to see the progress clearly!
// Common color combinations:
// - Red (#FF0000) for future, Gray (#CCCCCC) for past
// - Blue (#2196F3) for future, Light Blue (#BBDEFB) for past
// - Green (#4CAF50) for future, Light Green (#C8E6C9) for past
String nextStepsRouteColor = "#FF0000"; // Red for future steps
String prevStepsRouteColor = "#CCCCCC"; // Gray for completed steps
double polylineWidth = 10;
// 🆕 DEBUG: Log the colors being sent to native
print('🎨 Route colors - nextSteps: $nextStepsRouteColor, prevSteps: $prevStepsRouteColor');
// Create route configuration
final RouteConfiguration routeConfig = RouteConfiguration(
mapId: _mapWidget!.uuid,
initialLocation: _currentPosition!.location,
finalLocation: destination.position,
polylineWidth: polylineWidth,
nextStepsRouteColor: nextStepsRouteColor, // 🎨 Future route color
prevStepsRouteColor: prevStepsRouteColor, // 🎨 Completed route color
);
try {
// Create route
final LzRoute? route = await _mapWidget!.addRoute(routeConfig);
if (route != null) {
setState(() {
_currentRoute = route;
});
print('✅ Route created successfully!');
// 🆕 KEY: Configure location provider for automatic updates
await _configureRouteLocationProvider();
} else {
print('❌ Route creation failed - null response');
}
} catch (e) {
print('❌ Route creation failed: $e');
}
}
// 🆕 KEY FUNCTION: Configure location provider for routes
Future<void> _configureRouteLocationProvider() async {
try {
print('🔧 Configuring location provider for route updates...');
// Stop basic tracking
await _lazarilloSDK.stopUpdatingLocation();
// Start route-specific tracking
await _lazarilloSDK.startUpdatingLocation((Position position) {
setState(() {
_currentPosition = position;
});
// UPDATE CURRENT ROUTE STEP
if (position.routingStatus.status != RoutingStatus.noRoute) {
_currentStepNotifier.value = position.routingStatus.currentStep;
print('🔄 Route step updated: ${position.routingStatus.currentStep}');
} else {
_currentStepNotifier.value = null;
}
// Additional logging for debugging
if (position.location != null) {
print('📍 Location: ${position.location!.latitude.toStringAsFixed(4)}, ${position.location!.longitude.toStringAsFixed(4)}');
}
if (position.routingStatus.status != RoutingStatus.noRoute) {
print('📍 RouteStatus: Step ${position.routingStatus.currentStep}, Status: ${position.routingStatus.status}');
}
});
print('✅ Location provider configured for route updates!');
} catch (e) {
print('⚠️ Warning: Could not configure location provider: $e');
}
}4. Route Cleanup
Future<void> clearCurrentRoute() async {
try {
print('🧹 Clearing current route...');
// Clear route from map
if (_mapWidget != null) {
await _mapWidget!.clearRoute();
}
// Stop route-specific location tracking
await _lazarilloSDK.stopUpdatingLocation();
// Clear state
setState(() {
_currentRoute = null;
_currentStepNotifier.value = null;
});
print('✅ Route cleared successfully');
// Restart basic location tracking
_initializeLocationTracking();
} catch (e) {
print('❌ Error clearing route: $e');
}
}5. UI for Progress Display
Widget _buildRouteProgressWidget() {
return ValueListenableBuilder<int?>(
valueListenable: _currentStepNotifier,
builder: (context, currentStep, _) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'🧭 Route Progress',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
// Current route step
Row(
children: [
const Icon(Icons.route, color: Colors.blue),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
currentStep != null
? 'Current Step: $currentStep'
: 'No active route',
style: const TextStyle(fontSize: 16),
),
if (currentStep != null && _currentRoute != null)
Text(
'Progress: ${((currentStep + 1) / _currentRoute!.legs.first.steps.length * 100).toStringAsFixed(1)}%',
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
),
),
],
),
const SizedBox(height: 8),
// Route controls
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _currentRoute != null
? () => _showRouteInstructions()
: null,
icon: const Icon(Icons.list),
label: const Text('View Instructions'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton.icon(
onPressed: _currentRoute != null
? () => clearCurrentRoute()
: null,
icon: const Icon(Icons.clear),
label: const Text('Clear Route'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
),
),
],
),
],
),
),
);
},
);
}
void _showRouteInstructions() {
if (_currentRoute != null && _currentRoute!.legs.isNotEmpty) {
final instructions = _currentRoute!.legs.first.steps
.map((step) => step.htmlInstruction.replaceAll(RegExp(r'<[^>]*>'), ''))
.where((s) => s.isNotEmpty)
.toList();
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Route Instructions'),
content: SizedBox(
width: double.maxFinite,
child: ListView.separated(
shrinkWrap: true,
itemCount: instructions.length,
separatorBuilder: (_, __) => const Divider(),
itemBuilder: (context, index) {
return ListTile(
leading: Text('${index + 1}'),
title: Text(instructions[index]),
);
},
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Close'),
),
],
);
},
);
}
}🔄 Complete Flow
- Initialization: Configure SDK and map
- Route Creation: Use
addRoute()withRouteConfiguration - Provider Configuration: Call
_configureRouteLocationProvider() - Automatic Updates: Route progress updates automatically
- Cleanup: Use
clearCurrentRoute()when needed
📋 Key Differences
| Aspect | Without Provider | With Provider |
|---|---|---|
| Route Progress | ❌ Static | ✅ Updates automatically |
| Visual Progress | ❌ Not visible | ✅ Visible in real-time |
| User Experience | ❌ Basic | ✅ Professional |
🧪 Testing
To verify it works correctly:
- Create a route using
navigateToPlace() - Verify that ”✅ Location provider configured for route updates!” appears
- Simulate user movement
- Verify that route progress updates automatically
- Verify that current step updates in the UI
- Use
clearCurrentRoute()to clean up
🔧 Troubleshooting
Route Colors Not Updating
If you see both route segments with the same color, check:
-
Color Configuration: Ensure
nextStepsRouteColorandprevStepsRouteColorare different:// ✅ Correct - Different colors String nextStepsRouteColor = "#FF0000"; // Red String prevStepsRouteColor = "#CCCCCC"; // Gray // ❌ Wrong - Same colors String nextStepsRouteColor = "#FF0000"; // Red String prevStepsRouteColor = "#FF0000"; // Red (same!) -
Debug Logs: Check the console for these logs:
🎨 Route colors - nextSteps: #FF0000, prevSteps: #CCCCCC 🔧 Route colors from Flutter - nextSteps: #FF0000, prevSteps: #CCCCCC -
Location Provider: Ensure
_configureRouteLocationProvider()is called after route creation
Common Issues
- Both segments red:
prevStepsRouteColoris not being set correctly - No color difference: Colors are too similar (e.g., #FF0000 and #FF0001)
- Colors not updating: Location provider not configured properly
🎯 Result
With this implementation, routes in Flutter will have the same route progress update functionality as native applications, providing a consistent and professional navigation experience.