# Search Along Route

Last updated:

<!-- -->

October 18, 2026

<!-- -->

\| 8 minutes read

<br />

<br />

In this guide, you will learn how to calculate a route, simulate navigation, and search for landmarks along the route.

## How it works[​](#how-it-works "Direct link to How it works")

This example demonstrates the following key features:

* Calculates a route between two landmarks and displays it on a map.
* Simulates navigation along the route, allowing you to start and stop navigation.
* Provides a feature to search for landmarks along the calculated route and displays the results.

![Initial map view](/docs/flutter/assets/images/example_flutter_search_along_route1-e8a8b6c310c9a063a8b1d827086bb239.png)

**Initial map view**

![Map with computed route](/docs/flutter/assets/images/example_flutter_search_along_route2-3be2673f0c03084d2da7950ba6697b69.png)

**Map with computed route**

![Navigating on route, search button available](/docs/flutter/assets/images/example_flutter_search_along_route3-3b2b97db2b098482133914b326acad62.png)

**Navigating on route, search button available**

![Search results dialog](/docs/flutter/assets/images/example_flutter_search_along_route4-9ee7a16d8b35cdfa180a9cb10e0c823e.png)

**Search results dialog**

### UI and Map Integration[​](#ui-and-map-integration "Direct link to UI and Map Integration")

main.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/search_along_route/lib/main.dart?ref_type=heads#L20)

```dart
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Search Along Route',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late GemMapController _mapController;

  bool _isSimulationActive = false;
  bool _areRoutesBuilt = false;

  // We use the handler to cancel the route calculation.
  TaskHandler? _routingHandler;

  // We use the handler to cancel the navigation.
  TaskHandler? _navigationHandler;

  @override
  void dispose() {
    GemKit.release();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.deepPurple[900],
        title: const Text(
          "Search Along Route",
          style: TextStyle(color: Colors.white),
        ),
        leading: Row(
          children: [
            if (_areRoutesBuilt)
              IconButton(
                onPressed: _searchAlongRoute,
                icon: const Icon(Icons.search, color: Colors.white),
              ),
          ],
        ),
        actions: [
          if (!_isSimulationActive && _areRoutesBuilt)
            IconButton(
              onPressed: _startSimulation,
              icon: const Icon(Icons.play_arrow, color: Colors.white),
            ),
          if (_isSimulationActive)
            IconButton(
              onPressed: _stopSimulation,
              icon: const Icon(Icons.stop, color: Colors.white),
            ),
          if (!_areRoutesBuilt)
            IconButton(
              onPressed: () => _onBuildRouteButtonPressed(),
              icon: const Icon(Icons.route, color: Colors.white),
            ),
        ],
      ),
      body: GemMap(
        key: ValueKey("GemMap"),
        onMapCreated: _onMapCreated,
        appAuthorization: projectApiToken,
      ),
    );
  }

```

### Route calculation[​](#route-calculation "Direct link to Route calculation")

This section shows how to calculate a route between two landmarks and display it on the map.

main.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/search_along_route/lib/main.dart?ref_type=heads#L107)

```dart
  Future<void> _onBuildRouteButtonPressed() async {
    // Define the departure.
    final departureLandmark = Landmark.withLatLng(
      latitude: 37.77903,
      longitude: -122.41991,
    );

    // Define the destination.
    final destinationLandmark = Landmark.withLatLng(
      latitude: 37.33619,
      longitude: -121.89058,
    );

    // Define the route preferences.
    final routePreferences = RoutePreferences();
    _showSnackBar(context, message: 'The route is calculating.');

    _routingHandler = RoutingService.calculateRoute(
      [departureLandmark, destinationLandmark],
      routePreferences,
      (err, routes) async {
        // If the route calculation is finished, we don't have a progress listener anymore.
        _routingHandler = null;

        ScaffoldMessenger.of(context).clearSnackBars();

        // If there aren't any errors, we display the routes.
        if (err == GemError.success) {
          // Get the routes collection from map preferences.
          final routesMap = _mapController.preferences.routes;

          // Display the routes on map.
          for (final route in routes) {
            routesMap.add(
              route,
              route == routes.first,
              label: getMapLabel(route),
            );
          }

          _mapController.centerOnRoute(routes.first);
        }

        setState(() {
          _areRoutesBuilt = true;
        });
      },
    );
  }

  // Start simulated navigation.
  void _startSimulation() {
    if (_isSimulationActive) return;
    if (!_areRoutesBuilt) return;

    _mapController.preferences.routes.clearAllButMainRoute();
    final routes = _mapController.preferences.routes;

    if (routes.mainRoute == null) {
      _showSnackBar(context, message: "No main route available");
      return;
    }

    _navigationHandler = NavigationService.startSimulation(
      routes.mainRoute!,
      onNavigationInstruction: (instruction, events) {
        setState(() {
          _isSimulationActive = true;
        });
      },
      onError: (error) {
        // If the navigation has ended or if and error occurred while navigating, remove routes.
        setState(() {
          _isSimulationActive = false;
          _cancelRoute();
        });

        if (error != GemError.cancel) {
          _stopSimulation();
        }
        return;
      },
    );

    // Set the camera to follow position.
    _mapController.startFollowingPosition();

    setState(() {
      _isSimulationActive = true;
    });
  }

```

### Navigation Simulation[​](#navigation-simulation "Direct link to Navigation Simulation")

This section demonstrates how to start and stop a simulated navigation along the calculated route.

main.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/search_along_route/lib/main.dart?ref_type=heads#L157)

```dart
  // Start simulated navigation.
  void _startSimulation() {
    if (_isSimulationActive) return;
    if (!_areRoutesBuilt) return;

    _mapController.preferences.routes.clearAllButMainRoute();
    final routes = _mapController.preferences.routes;

    if (routes.mainRoute == null) {
      _showSnackBar(context, message: "No main route available");
      return;
    }

    _navigationHandler = NavigationService.startSimulation(
      routes.mainRoute!,
      onNavigationInstruction: (instruction, events) {
        setState(() {
          _isSimulationActive = true;
        });
      },
      onError: (error) {
        // If the navigation has ended or if and error occurred while navigating, remove routes.
        setState(() {
          _isSimulationActive = false;
          _cancelRoute();
        });

        if (error != GemError.cancel) {
          _stopSimulation();
        }
        return;
      },
    );

    // Set the camera to follow position.
    _mapController.startFollowingPosition();

    setState(() {
      _isSimulationActive = true;
    });
  }

  void _cancelRoute() {
    // Remove the routes from map.
    _mapController.preferences.routes.clear();

    if (_routingHandler != null) {
      // Cancel the navigation.
      RoutingService.cancelRoute(_routingHandler!);
      _routingHandler = null;
    }

    setState(() {
      _areRoutesBuilt = false;
    });
  }

  // Stop simulated navigation.
  void _stopSimulation() {
    // Cancel the navigation.
    NavigationService.cancelNavigation(_navigationHandler);
    _navigationHandler = null;

    _cancelRoute();

    setState(() {
      _isSimulationActive = false;
      _areRoutesBuilt = false;
    });
  }

```

### Search Along Route[​](#search-along-route-1 "Direct link to Search Along Route")

The following code shows how to search for landmarks along the calculated route. The search results are printed to the console and shown in a popup dialog on the screen.

main.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/search_along_route/lib/main.dart?ref_type=heads#L229)

```dart
  void _searchAlongRoute() {
    if (!_areRoutesBuilt) return;

    final routes = _mapController.preferences.routes;

    if (routes.mainRoute == null) {
      _showSnackBar(context, message: "No main route available");
      return;
    }

    _showSnackBar(context, message: 'Searching along route...');

    // Calling the search along route SDK method.
    // (err, results) - is a callback function that gets called when the search is finished.
    // err is an error enum, results is a list of landmarks.
    SearchService.searchAlongRoute(routes.mainRoute!, (err, results) {
      ScaffoldMessenger.of(context).clearSnackBars();

      if (err != GemError.success) {
        print("SearchAlongRoute - no results found");
        _showSnackBar(
          context,
          message: "No results found",
          duration: const Duration(seconds: 2),
        );
        return;
      }

      print("SearchAlongRoute - ${results.length} results:");
      for (final Landmark landmark in results) {
        final landmarkName = landmark.name;
        print("SearchAlongRoute: $landmarkName");
      }

      // Show the results in a popup dialog
      _showSearchResultsDialog(results);
    });
  }

```

tip

The result of the search operation is written in the console.

### Search Results Dialog[​](#search-results-dialog "Direct link to Search Results Dialog")

Create a popup dialog to display the search results. Each result is shown as a card with the landmark's name, address, coordinates, and category icon. Tapping the trailing icon centers the map on the landmark's location.

main.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/search_along_route/lib/main.dart?ref_type=heads#L269)

```dart
  void _showSearchResultsDialog(List<Landmark> results) {
    showModalBottomSheet<void>(
      context: context,
      isScrollControlled: true,
      backgroundColor: Colors.transparent,
      builder: (context) => DraggableScrollableSheet(
        initialChildSize: 0.5,
        minChildSize: 0.3,
        maxChildSize: 0.9,
        builder: (context, scrollController) => Container(
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
            boxShadow: [
              BoxShadow(
                color: Colors.black.withValues(alpha: 0.2),
                blurRadius: 10,
                spreadRadius: 2,
              ),
            ],
          ),
          child: Column(
            children: [
              // Handle bar
              Container(
                margin: const EdgeInsets.only(top: 12, bottom: 8),
                width: 40,
                height: 4,
                decoration: BoxDecoration(
                  color: Colors.grey[300],
                  borderRadius: BorderRadius.circular(2),
                ),
              ),
              // Header
              Padding(
                padding: const EdgeInsets.symmetric(
                  horizontal: 16,
                  vertical: 8,
                ),
                child: Row(
                  children: [
                    Icon(Icons.location_on, color: Colors.deepPurple[900]),
                    const SizedBox(width: 8),
                    Text(
                      'Places Along Route',
                      style: TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                        color: Colors.deepPurple[900],
                      ),
                    ),
                    const Spacer(),
                    Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 12,
                        vertical: 4,
                      ),
                      decoration: BoxDecoration(
                        color: Colors.deepPurple[100],
                        borderRadius: BorderRadius.circular(12),
                      ),
                      child: Text(
                        '${results.length} found',
                        style: TextStyle(
                          color: Colors.deepPurple[900],
                          fontWeight: FontWeight.w600,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
              const Divider(),
              // Results list
              Expanded(
                child: ListView.builder(
                  controller: scrollController,
                  padding: const EdgeInsets.symmetric(horizontal: 16),
                  itemCount: results.length,
                  itemBuilder: (context, index) {
                    final landmark = results[index];
                    return _buildLandmarkTile(landmark, index);
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  // Build a tile for each landmark
  Widget _buildLandmarkTile(Landmark landmark, int index) {
    final coordinates = landmark.coordinates;
    final address = landmark.address;

    return Card(
      margin: const EdgeInsets.symmetric(vertical: 6),
      elevation: 2,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      child: ListTile(
        contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
        leading: _buildLandmarkIcon(landmark, index),
        title: Text(
          landmark.name.isNotEmpty ? landmark.name : 'Unknown Place',
          style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
        ),
        subtitle: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Padding(
              padding: const EdgeInsets.only(top: 4),
              child: Row(
                children: [
                  Icon(Icons.place, size: 14, color: Colors.grey[600]),
                  const SizedBox(width: 4),
                  Expanded(
                    child: Text(
                      address.format(),
                      style: TextStyle(color: Colors.grey[600], fontSize: 13),
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                    ),
                  ),
                ],
              ),
            ),
            Padding(
              padding: const EdgeInsets.only(top: 4),
              child: Row(
                children: [
                  Icon(Icons.gps_fixed, size: 14, color: Colors.grey[500]),
                  const SizedBox(width: 4),
                  Text(
                    '${coordinates.latitude.toStringAsFixed(5)}, ${coordinates.longitude.toStringAsFixed(5)}',
                    style: TextStyle(color: Colors.grey[500], fontSize: 12),
                  ),
                ],
              ),
            ),
          ],
        ),
        trailing: IconButton(
          icon: Icon(Icons.center_focus_strong, color: Colors.deepPurple[700]),
          onPressed: () {
            Navigator.pop(context);
            _mapController.centerOnCoordinates(coordinates);
          },
        ),
      ),
    );
  }

  // Build icon for landmark from category image
  Widget _buildLandmarkIcon(Landmark landmark, int index) {
    if (landmark.categories.isNotEmpty) {
      final img = landmark.categories.first.img;
      if (img.isValid) {
        final bytes = img.getRenderableImageBytes(size: const ui.Size(48, 48));
        if (bytes != null) {
          return CircleAvatar(
            backgroundColor: Colors.grey[200],
            child: Image.memory(
              bytes,
              width: 32,
              height: 32,
              errorBuilder: (context, error, stackTrace) =>
                  _buildDefaultIcon(index),
            ),
          );
        }
      }
    }
    return _buildDefaultIcon(index);
  }

  // Build default numbered icon
  Widget _buildDefaultIcon(int index) {
    return CircleAvatar(
      backgroundColor: Colors.deepPurple[900],
      child: Text(
        '${index + 1}',
        style: const TextStyle(
          color: Colors.white,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }

```

### Utility Functions[​](#utility-functions "Direct link to Utility Functions")

Utility functions are defined to show messages and format route labels.

main.dart[View on Github](https://github.com/magiclane/magiclane-maps-sdk-examples-for-flutter/tree/main/search_along_route/lib/main.dart?ref_type=heads#L466)

```dart
    final snackBar = SnackBar(content: Text(message), duration: duration);

    ScaffoldMessenger.of(context).showSnackBar(snackBar);
  }
}

String getMapLabel(Route route) {
  return '${convertDistance(route.getTimeDistance().totalDistanceM)} \n${convertDuration(route.getTimeDistance().totalTimeS)}';
}

// Utility function to convert the meters distance into a suitable format.
String convertDistance(int meters) {
  if (meters >= 1000) {
    double kilometers = meters / 1000;
    return '${kilometers.toStringAsFixed(1)} km';
  } else {
    return '${meters.toString()} m';
  }
}

// Utility function to convert the seconds duration into a suitable format.
String convertDuration(int seconds) {
  int hours = seconds ~/ 3600; // Number of whole hours
  int minutes = (seconds % 3600) ~/ 60; // Number of whole minutes

  String hoursText = (hours > 0) ? '$hours h ' : ''; // Hours text
  String minutesText = '$minutes min'; // Minutes text

  return hoursText + minutesText;
}

```
