# Offline Routing

Last updated:

<!-- -->

October 18, 2026

<!-- -->

\| 5 minutes read

<br />

<br />

In this guide, you will learn how to implement offline routing functionality using the Maps SDK for Flutter. This example demonstrates how to download a map for offline use, disable internet access, and calculate routes offline.

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

This example demonstrates the following features:

* Download specific maps (e.g., for “Andorra”) to enable offline functionality.
* Disable internet access after successful map download to enforce offline usage.
* Compute routes offline between predefined waypoints, using the downloaded map data.

![Initial map view](/docs/flutter/assets/images/example_flutter_offline_routing1-0bcadc664a1cffa8142eef131215924b.png)

**Initial map view**

![Map downloaded](/docs/flutter/assets/images/example_flutter_offline_routing2-6521db91756033681f9fff394b1e75d7.png)

**Map downloaded**

![Computed route inside downloaded region](/docs/flutter/assets/images/example_flutter_offline_routing3-1123a1ee0318cd0a183475dcd8df9638.png)

**Computed route inside downloaded region**

### 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/offline_routing/lib/main.dart?ref_type=heads#L14)

```dart
void main() {
  runApp(const MyApp());
}

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

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

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

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

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

  bool _areRoutesBuilt = false;

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

  bool _isDownloaded = false;
  double _downloadProgress = 0;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.deepPurple[900],
        title: const Text(
          'Offline Routing',
          style: TextStyle(color: Colors.white),
        ),
        actions: [
          // Map is downloading.
          if (_isDownloaded == false && _downloadProgress != 0)
            Container(
              width: 20,
              height: 20,
              margin: const EdgeInsets.only(right: 10.0),
              child: const Center(
                child: CircularProgressIndicator(color: Colors.white),
              ),
            ),
          // Map is not downloaded.
          if (_isDownloaded == false && _downloadProgress == 0)
            IconButton(
              onPressed: () => _setOfflineMap(),
              icon: const Icon(Icons.download, color: Colors.white),
            ),
          // Routes are not built.
          if (_routingHandler == null &&
              _areRoutesBuilt == false &&
              _isDownloaded == true)
            IconButton(
              onPressed: () => _onBuildRouteButtonPressed(context),
              icon: const Icon(Icons.route, color: Colors.white),
            ),
          // Routes calculating is in progress.
          if (_routingHandler != null)
            IconButton(
              onPressed: () => _onCancelRouteButtonPressed(),
              icon: const Icon(Icons.stop, color: Colors.white),
            ),
          // Routes calculating is finished.
          if (_areRoutesBuilt == true)
            IconButton(
              onPressed: () => _onClearRoutesButtonPressed(),
              icon: const Icon(Icons.clear, color: Colors.white),
            ),
        ],
      ),
      body: GemMap(
        key: ValueKey("GemMap"),
        onMapCreated: _onMapCreated,
        appAuthorization: projectApiToken,
      ),
    );
  }

  void _onMapCreated(GemMapController controller) async {
    // Save controller for further usage.
    _mapController = controller;

    SdkSettings.setAllowOffboardServiceOnExtraChargedNetwork(
      ServiceGroupType.contentService,
      true,
    );
  }

```

### Define Route Calculation Logic[​](#define-route-calculation-logic "Direct link to Define Route Calculation Logic")

Implement methods to build routes based on predefined waypoints and manage the download of maps.

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

```dart
  void _onBuildRouteButtonPressed(BuildContext context) {
    // Define the departure.
    final departureLandmark = Landmark.withLatLng(
      latitude: 42.49720,
      longitude: 1.50498,
    );

    // Define the destination.
    final destinationLandmark = Landmark.withLatLng(
      latitude: 42.51003,
      longitude: 1.53400,
    );

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

    _showSnackBar(context, message: "The route is being calculated.");

    // 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.

    _routingHandler = RoutingService.calculateRoute(
      [departureLandmark, destinationLandmark],
      routePreferences,
      (err, routes) {
        // 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),
            );
          }

          // Center the camera on routes.
          _mapController.centerOnRoutes(routes: routes);
          setState(() {
            _areRoutesBuilt = true;
          });
        }
      },
    );

    setState(() {});
  }

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

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

  void _onCancelRouteButtonPressed() {
    // If we have a progress listener we cancel the route calculation.
    if (_routingHandler != null) {
      RoutingService.cancelRoute(_routingHandler!);

      setState(() {
        _routingHandler = null;
      });
    }
  }

```

### Define Map Downloading Logic[​](#define-map-downloading-logic "Direct link to Define Map Downloading Logic")

Implement methods for downloading and managing the offline map.

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

```dart
  Future<List<ContentStoreItem>> _getMaps() async {
    Completer<List<ContentStoreItem>> mapsList =
        Completer<List<ContentStoreItem>>();

    ContentStore.asyncGetStoreContentList(ContentType.roadMap, (
      err,
      items,
      isCached,
    ) {
      if (err == GemError.success && items.isNotEmpty) {
        mapsList.complete(items);
      }
    });
    return mapsList.future;
  }

  // Check and download Andorra's map if it's not already downloaded on the device.
  void _setOfflineMap() {
    // Method to load the local maps
    final localMaps = ContentStore.getLocalContentList(ContentType.roadMap);

    // Check if map is already downloaded
    if (localMaps.where((map) => map.name == 'Andorra').isNotEmpty) {
      setState(() {
        _isDownloaded = true;
      });

      // Deny internet connection
      SdkSettings.setAllowInternetConnection(false);
      return;
    }

    _getMaps().then((maps) {
      _downloadProgress = maps[4].downloadProgress.toDouble();
      _downloadMap(maps[4]);
    });
  }

  void _downloadMap(ContentStoreItem map) {
    // Download the map.
    map.asyncDownload(
      _onMapDownloadFinished,
      onProgress: _onMapDownloadProgressUpdated,
      allowChargedNetworks: true,
    );
  }

  void _onMapDownloadProgressUpdated(int progress) {
    setState(() => _downloadProgress = progress.toDouble());
  }

  void _onMapDownloadFinished(GemError err) {
    // If there is no error, we change the state
    if (err == GemError.success) {
      // Deny internet connection
      SdkSettings.setAllowInternetConnection(false);

      setState(() => _isDownloaded = true);
    }
  }

```

### Show SnackBar for User Feedback[​](#show-snackbar-for-user-feedback "Direct link to Show SnackBar for User Feedback")

Implement a method to show a SnackBar for providing feedback to the user.

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

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

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

```

### Utility methods for Route Labeling[​](#utility-methods-for-route-labeling "Direct link to Utility methods for Route Labeling")

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

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


```
