Skip to content

How to Use Maps Offline with Flutter MapLibre GL

Free

Starter

Standard

Professional

You're building a beautiful mobile application with Flutter MapLibre GL. You're just about to wrap up the meeting to finalize the requirements when someone says asks "what happens if the user loses network connectivity?" Spotty networks and even intentional offline use are one of the challenges mobile developers are often expected to deal with. This tutorial will walk you through one way to improve your map-centric user experience: offline tile caching.

This tutorial will assume you already have a project set up with Flutter MapLibre GL. If you're just getting started, you might want to check out the Flutter MapLibre GL Quickstart first.

Downloading Tiles

To enable offline use of our application, we'll use the downloadOfflineRegion method. This method takes three arguments: a region definition, metadata, and a callback function to receive status updates. The last two arguments are technically optional, but you probably want them. Let's dive in with some code.

Specifying the Offline Region

// Bounding box around Manhattan. Note that this will consume
// approximately 200 API credits.
final bounds = LatLngBounds(
  southwest: const LatLng(40.69, -74.03),
  northeast: const LatLng(40.84, -73.86),
);
final regionDefinition = OfflineRegionDefinition(
    bounds: bounds, mapStyleUrl: _mapStyleUrl(), minZoom: 6, maxZoom: 14);

The underlying offline manager (from MapLibre Native) describes offline regions in terms of bounding boxes. Even if your area of interest is a more complex shape, you'll need to turn it into a box, defined by a southwest and northeast point.

The style URL is a Stadia Maps style URL. Note that you'll need to authenticate in order to download the tiles. The most convenient way of doing this is to include your API key in the query string like so: https://tiles.stadiamaps.com/styles/alidade_smooth_dark.json?api_key=YOUR-API-KEY. If you are using a custom style, make sure that the source URL includes the API key as described in our guide to custom map styling.

Info

The Stadia Maps Terms of Service allow for limited caching of map tiles on mobile devices, subject to certain conditions. To give a rough scale, you can download an area approximately the size of Long Island (if you are using vector tiles) for offline use to a user's device.

On-device is subject to the following conditions:

  1. You must have an active subscription to Stadia Maps covering the tiles you are caching.
  2. You must limit caching to 100 MB of map tiles per device.
  3. You must observe the HTTP cache headers sent by our servers. You must attempt revalidate expired cache entries whenever possible, but may serve a stale tile if the user's device is offline.

The MapLibre Native offline storage engine in its default configuration is within our limits.

Metadata

We won't spend a lot of time on metadata. It is completely optional, and lets you specify a Map<String, dynamic> that is stored with the offline region. This is most frequently used to attach a name or other identifier for future use when you want to manage downloaded regions (for example, deleting them when they are no longer needed).

Event Callback

The final argument to downloadOfflineRegion is a callback. This lets you update your app's UI when progress is made, the download finishes, or an error occurs. It usually looks something like this (variables and methods inside the closure are placeholders for your own).

void _onDownloadEvent(DownloadRegionStatus status) {
  if (status is maplibre_gl.Success) {
    setState(() {
      // Update some state variables in the widget to indicate the download completed.
    });

    // Update your UI, display a StackBar notification, etc.
  } else if (status is maplibre_gl.Error) {
    setState(() {
      // Update some state variables in the widget to indicate the download failed,
      // reset progress indicators, etc.
    });

    // Update your UI, display a StackBar notification, etc.
  } else if (status is maplibre_gl.InProgress) {
    setState(() {
      // Update state, such as a download progress indicator. The reported values are
      // in the range 0-100, so you'll need to divide by 100 to use with many standard
      // progress widgets.
      downloadProgress = status.progress / 100;
    });
  }
}

Download Invocation

Now that we have a region definition, some metadata, and a callback function, we can call the downloadOfflineRegion function.

final region = await downloadOfflineRegion(regionDefinition,
    metadata: {
      'name': 'Manhattan',
    },
    onEvent: _onDownloadEvent);

While this is an async function, it actually returns fairly quickly. This lets us get the region info, which includes a unique identifier and a few other pieces on information, even before the download finishes.

Removing Unneeded Tiles

Let's imagine now that your user has moved away from New York to become a digital nomad and no longer needs these map tiles cluttering up their device. How do we remove them?

Getting a List of Offline Regions

The underlying MapLibre Native library is capable of storing multiple distinct regions offline. When it's time to clean up unneeded tiles, you must tell it which previously downloaded region to discard.

final regions = await getListOfRegions();
for (final region in regions) {
    // Is this the region we're looking for?
}

Once you have the list of regions from getListOfRegions, you can filter it if desired (ex: using the metadata you set when starting the download).

Deleting a region

Once you've decided on a region to delete, the rest is pretty simple...

await deleteOfflineRegion(region.id);

... or is it? The term "delete" is unfortunately a bit of a misnomer. Quoting the MapLibre Native documentation:

When you remove an offline pack, any resources that are required by that pack, but not other packs, become eligible for deletion from offline storage. Because the backing store used for offline storage is also used as a general purpose cache for map resources, such resources may not be immediately removed if the implementation determines that they remain useful for general performance of the map.

So the data will not necessarily be deleted right away; just marked as able to be freed up. Controls do exist for clearing the ambient cache as well, but these are not wrapped by Flutter MapLibre GL at this time. Normally you should not have to worry about this though, as the storage engine will clear the tiles out eventually if they are not used.

Example Repository

We have published a full example repository on GitHub which visualizes a set of earthquake data. This is analogous to the cluster example for MapLibre GL JS, and we've also included a very simple offline flow!

Tap the download button to cache Manhattan for offline use, watch the progress indicator as it downloads the tiles, and tap the trash can to clean up when you're done.

Next Steps

The project's GitHub repo is the authoritative source for documentation and further information. Additionally, browsable API documentation is hosted here (note that it is sometimes a bit slow to load).

And of course, don't forget to sign up for a free Stadia Maps account, so you can try this on your own device!

Get Started With a Free Account