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:
- You must have an active subscription to Stadia Maps covering the tiles you are caching.
- You must limit caching to 100 MB of map tiles per device.
- 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¶
The metadata argument is an optional Map<String, dynamic>
.
This key/value map is stored with the offline region.
You can use this to identify downloaded regions however you like,
and remove them once 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 iterate over them and remove the ones that you don't want anymore
(ex: filtering 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!