Skip to Content
ExamplesRoute Progress

🧭 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

  1. Initialization: Configure SDK and map
  2. Route Creation: Use addRoute() with RouteConfiguration
  3. Provider Configuration: Call _configureRouteLocationProvider()
  4. Automatic Updates: Route progress updates automatically
  5. Cleanup: Use clearCurrentRoute() when needed

📋 Key Differences

AspectWithout ProviderWith Provider
Route Progress❌ Static✅ Updates automatically
Visual Progress❌ Not visible✅ Visible in real-time
User Experience❌ Basic✅ Professional

🧪 Testing

To verify it works correctly:

  1. Create a route using navigateToPlace()
  2. Verify that ”✅ Location provider configured for route updates!” appears
  3. Simulate user movement
  4. Verify that route progress updates automatically
  5. Verify that current step updates in the UI
  6. Use clearCurrentRoute() to clean up

🔧 Troubleshooting

Route Colors Not Updating

If you see both route segments with the same color, check:

  1. Color Configuration: Ensure nextStepsRouteColor and prevStepsRouteColor are different:

    // ✅ Correct - Different colors String nextStepsRouteColor = "#FF0000"; // Red String prevStepsRouteColor = "#CCCCCC"; // Gray // ❌ Wrong - Same colors String nextStepsRouteColor = "#FF0000"; // Red String prevStepsRouteColor = "#FF0000"; // Red (same!)
  2. Debug Logs: Check the console for these logs:

    🎨 Route colors - nextSteps: #FF0000, prevSteps: #CCCCCC 🔧 Route colors from Flutter - nextSteps: #FF0000, prevSteps: #CCCCCC
  3. Location Provider: Ensure _configureRouteLocationProvider() is called after route creation

Common Issues

  • Both segments red: prevStepsRouteColor is 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.

Last updated on