# Multi Map Routing

Last updated:

<!-- -->

October 18, 2026

<!-- -->

\| 5 minutes read

<br />

<br />

In this guide, you will learn how to implement multi-map routing functionality using the Maps SDK for Flutter. This example demonstrates how to manage routes on two maps simultaneously.

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

This example demonstrates the following features:

* Interact with and manage routing functionalities on two separate maps within the same application.
* Create and calculate routes with specified waypoints and preferences for each map independently.

![Initial maps screen](/docs/flutter/assets/images/example_flutter_multi_map_routing1-3417c499abaafa37daf3b84185828273.png)

**Initial maps screen**

![Computed route of top map](/docs/flutter/assets/images/example_flutter_multi_map_routing2-040772119c22e4d2d96e0f1c1369f94f.png)

**Computed route of top map**

![Computed routes on both maps](/docs/flutter/assets/images/example_flutter_multi_map_routing3-37deb99d1c63120df4542b6fafde3422.png)

**Computed routes on both maps**

### Build the Main Application[​](#build-the-main-application "Direct link to Build the Main Application")

Define the main application widget, MyApp.

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Multi Map Routing',
      debugShowCheckedModeBanner: false,
      home: MyHomePage(),
    );
  }
}

```

### Handle Maps and Routes in the Stateful Widget[​](#handle-maps-and-routes-in-the-stateful-widget "Direct link to Handle Maps and Routes in the Stateful Widget")

Create the stateful widget, MyHomePage , which will handle two maps and their respective routes.

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

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

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

```

### Define State Variables and Methods[​](#define-state-variables-and-methods "Direct link to Define State Variables and Methods")

Within \_MyHomePageState , define the necessary state variables and methods to interact with the maps and manage routes.

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

```dart
class _MyHomePageState extends State<MyHomePage> {
  late GemMapController _mapController1;
  late GemMapController _mapController2;

  // We use the handlers to cancel the route calculation.
  TaskHandler? _routingHandler1;
  TaskHandler? _routingHandler2;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.deepPurple[900],
        title: const Text(
          'Multi Map Routing',
          style: TextStyle(color: Colors.white),
        ),
        leading: IconButton(
          onPressed: _removeRoutes,
          icon: const Icon(Icons.close, color: Colors.white),
        ),
        actions: [
          IconButton(
            onPressed: () => _onBuildRouteButtonPressed(true),
            icon: const Icon(Icons.route, color: Colors.white),
          ),
          IconButton(
            onPressed: () => _onBuildRouteButtonPressed(false),
            icon: const Icon(Icons.route, color: Colors.white),
          ),
        ],
      ),
      body: Column(
        children: [
          Expanded(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: GemMap(
                key: ValueKey("GemMap"),
                onMapCreated: _onMap1Created,
                appAuthorization: projectApiToken,
              ),
            ),
          ),
          Expanded(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: GemMap(onMapCreated: _onMap2Created),
            ),
          ),
        ],
      ),
    );
  }

  void _showSnackBar(
    BuildContext context, {
    required String message,
    Duration duration = const Duration(hours: 1),
  }) {
    final snackBar = SnackBar(content: Text(message), duration: duration);

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

  // The callback for when map 1 is ready to use.
  void _onMap1Created(GemMapController controller) {
    // Save controller for further usage.
    _mapController1 = controller;
  }

  // The callback for when map 2  is ready to use.
  void _onMap2Created(GemMapController controller) {
    // Save controller for further usage.
    _mapController2 = controller;
  }

  void _onBuildRouteButtonPressed(bool isFirstMap) {
    final waypoints = <Landmark>[];
    if (isFirstMap) {
      // Define the departure.
      final departure = Landmark.withLatLng(
        latitude: 37.77903,
        longitude: -122.41991,
      );

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

      waypoints.add(departure);
      waypoints.add(destination);
    } else {
      // Define the departure.
      final departure = Landmark.withLatLng(
        latitude: 51.50732,
        longitude: -0.12765,
      );

      // Define the destination.
      final destination = Landmark.withLatLng(
        latitude: 51.27483,
        longitude: 0.52316,
      );

      waypoints.add(departure);
      waypoints.add(destination);
    }

    // Define the route preferences.
    final routePreferences = RoutePreferences();

    _showSnackBar(
      context,
      message: isFirstMap
          ? 'The first route is calculating.'
          : 'The second route is calculating.',
    );

    // Calling the calculateRoute SDK method.
    // (err, results) - is a callback function that gets called when the route computing is finished.
    // err is an error enum, results is a list of routes.
    if (isFirstMap) {
      _routingHandler1 = RoutingService.calculateRoute(
        waypoints,
        routePreferences,
        (err, routes) => _onRouteBuiltFinished(err, routes, true),
      );
    } else {
      _routingHandler2 = RoutingService.calculateRoute(
        waypoints,
        routePreferences,
        (err, routes) => _onRouteBuiltFinished(err, routes, false),
      );
    }
  }

  void _onRouteBuiltFinished(
    GemError err,
    List<Route>? routes,
    bool isFirstMap,
  ) {
    // If the route calculation is finished, we don't have a progress listener anymore.
    if (isFirstMap) {
      _routingHandler1 = null;
    } else {
      _routingHandler2 = null;
    }

    ScaffoldMessenger.of(context).clearSnackBars();
    if (_routingHandler1 != null) {
      _showSnackBar(context, message: 'The first route is calculating.');
    }
    if (_routingHandler2 != null) {
      _showSnackBar(context, message: 'The second route is calculating.');
    }

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

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

      // Center the camera on routes.
      if (isFirstMap) {
        _mapController1.centerOnRoutes(routes: routes);
      } else {
        _mapController2.centerOnRoutes(routes: routes);
      }
    }
  }

  void _removeRoutes() {
    // If we have a progress listener we cancel the route calculation.

    if (_routingHandler1 != null) {
      RoutingService.cancelRoute(_routingHandler1!);
      _routingHandler1 = null;
    }

    if (_routingHandler2 != null) {
      RoutingService.cancelRoute(_routingHandler2!);
      _routingHandler2 = null;
    }

    // Remove the routes from map.
    _mapController1.preferences.routes.clear();
    _mapController2.preferences.routes.clear();
  }
}

```

### Displaying Route Information[​](#displaying-route-information "Direct link to Displaying Route Information")

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

```dart
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;
}

```
