Caching map tiles is one of the best ways to reduce data consumption and load maps quicker.
By caching tile maps, the user won’t have to re-download them every time it loads that part of the map. Ideally, every map app should cache tiles!
In this tutorial, I’ll explain how to cache maps using the flutter_map package and its plugin flutter_map_cache, which is super lightweight and licensed under a MIT license.
We’re also gonna use Hive, which is an easy and efficient key-value database.
*if you’re just looking for an updated sample code, click here to jump straight to it
Downloading the packages
1. type the following command in VS Code terminal to download the flutter_map package into your project:
flutter pub add flutter_map
2. now, type this command to download the flutter_map_cache plugin into your project:
flutter pub add flutter_map_cache
3. now download the path_provider package, which helps setting the location where the cache is gonna be stored:
flutter pub add path_provider
4. finally, enter the following command to download the dio_cache_interceptor_hive_store package, which manages map tile requests and stores them into a Hive database:
flutter pub add dio_cache_interceptor_hive_store
Implementation and Sample Code
Here’s a commented sample code that you can paste into your “main.dart” file if you want to test the package.
// first, you must import the downloaded packages
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_cache/flutter_map_cache.dart';
import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart';
// this variable will contain the cache folder path
String path = '';
void main() {
runApp(
MaterialApp(
home: Home(),
),
);
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
// the builder is extracted as a method to improve readability and organization
body: buildContainer());
}
// the builder was set as FutureBuilder because it needs to wait for the cache folder path to be set before showing the map on screen
buildContainer() {
return FutureBuilder(
future: getPath(),
builder: (context, snapshot) {
// if the map is loaded, the MainMap widget will be called
if (snapshot.hasData) {
return const MainMap();
} else {
// otherwise, it will show a loading animation
return const Center(
child: CircularProgressIndicator(),
);
}
});
}
// this function gets the device's folder and saves it into the "path" variable
Future<String> getPath() async {
final cacheDirectory = await getTemporaryDirectory();
path = cacheDirectory.path;
return cacheDirectory.path;
}
}
class MainMap extends StatelessWidget {
const MainMap({
super.key,
});
@override
@override
Widget build(BuildContext context) {
return FlutterMap(
options: const MapOptions(),
children: [
TileLayer(
// this is the address of the map tiles source we're using, OpenStreetMap
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
tileProvider: CachedTileProvider(
// maxStale keeps the tile cached for the given Duration and tries to revalidate the next time it gets requested
maxStale: const Duration(days: 30),
// HiveCacheStore sets the path and name of the cache file
store: HiveCacheStore(
path,
hiveBoxName: 'HiveCacheStore',
),
),
),
],
);
}
}
Possible Errors/Warnings
If you see the following warning:
The imported package 'latlong2' isn't a dependency of the importing package.
Try adding a dependency for 'latlong2' in the 'pubspec.yaml' file.
You just have to right-click the message and select “Add ‘latlong2’ to dependencies”
Conclusion
As you can see, it’s so easy to cache map tiles that you have no excuse not do it!
Your app’s users are gonna thank you and your map tiles provider will thank you even more! (unless they’re charging you for each download)