Mastering Offline Maps in Flutter: A Deep Dive (Part 2) — Flutter Map & FMTC

Lavkant Kachhwaha
Captainfresh Tech
Published in
4 min readJan 8, 2024

Welcome back to our journey of mastering offline maps in Flutter! In Part 1, we delved into the implementation of Mapbox GL for offline map caching, establishing a solid foundation for our Flutter project. Now, in Part 2, we shift our focus to the integration of the Flutter Map package, specifically exploring FMTC (Flutter Map Tile Caching). Let’s navigate through the improvements and optimizations we’ve made since our initial exploration.

Initialising the Flutter Map Package

dependencies:
flutter_map: ^0.16.0
flutter_map_tile_caching: ^1.0.1

After adding the dependencies, run flutter pub get to ensure they are installed.

Understanding Flutter Map

What is Flutter Map?

Flutter Map is an open-source, high-performance map library for Flutter apps. It enables you to easily integrate interactive maps with various features like:

  • Customisable map tiles: Choose from different map providers like OpenStreetMap or Mapbox and customise their appearance.
  • Marker support: Add markers with custom icons and information popups to mark specific locations.
  • Overlays and layers: Add additional layers like weather overlays, heatmaps, or custom vector graphics for richer map experiences.
  • Offline capabilities: Use tile caching libraries like FMTC (Flutter Map Tile Caching) to download and store map tiles for offline use.

Flutter Map offers excellent performance, smooth interactions, and a vibrant community, making it a popular choice for building location-based mobile apps.

Understanding FMTC (Flutter Map Tile Caching)

What is FMTC?

FMTC, or Flutter Map Tile Caching, is a powerful package that extends the capabilities of the Flutter Map package. It introduces tile caching, allowing you to download and store map tiles locally for offline use.

  1. Tile Download

FMTC downloads map tiles from a specified URL template. In our example, we use the OpenStreetMap tile server as the source.

urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',

2. Storage and Retrieval:

FMTC stores downloaded tiles in a local database, allowing quick retrieval and rendering even when offline.

tileProvider: FMTC.instance('offlineStoreName').getTileProvider(
FMTCTileProviderSettings(
behavior: CacheBehavior.cacheOnly,
cachedValidDuration: const Duration(days: 7),
maxStoreLength: 10000,
),
),

Database Used by FMTC

Isar Database Extremely fast, easy to use, and fully async NoSQL database for Flutter.
https://pub.dev/packages/isar

Downloading Tiles and Caching

Now, let’s dive into the process of downloading tiles and rendering them on the map. We’ll use the custom StoreService to download this tiles.

class StoreService {
Future<void> downloadTiles({
required StoreDirectory store,
required LatLngBounds bounds,
required int minZoom,
required int maxZoom,
required bool downloadForeground,
}) async {
final metadata = await store.metadata.readAsync;
final region = RegionService().getBaseMapRegionFromCoordinates(bounds);

if (downloadForeground) {
setDownloadProgress(
store.download
.startForeground(
region: region!.toDownloadable(minZoom, maxZoom, TileLayer(urlTemplate: metadata['sourceURL'])),
seaTileRemoval: false,
parallelThreads: 5,
)
.asBroadcastStream(),
);
} else {
store.download.startBackground(
region: region!.toDownloadable(minZoom, maxZoom, TileLayer(urlTemplate: metadata['sourceURL'])),
seaTileRemoval: false,
parallelThreads: 5,
);
}
}
}

What’s Happening:

1.Store Configuration:
Configuring the base map store with essential parameters like name, source URL, and more.

2. Store Initialization:
initializing the StoreService class to manage map stores.

3. Creating Base Map Store:
A method to create and configure the store for the base map.

4. Download Base Map:
Initiating the download of map tiles for the specified region.
Allowing users to choose between foreground and background downloads.

Why It Matters:

Efficient Map Handling: The code ensures a streamlined process for creating, configuring, and downloading map tiles.

User Preferences: Users can control the download behaviour — whether they want a real-time progress update or a silent background operation.

Rendering Tiles in Offline Map

Offline Map Initialization

class _OfflineMapScreenState extends State<OfflineMapScreen> {
// ...

@override
void initState() {
super.initState();
markerService.getMarkers();
// Listen to location changes
Geolocator.getPositionStream().listen((Position position) {
_currentPosition = position;
});
_getCurrentLocation();
}
// ...
}

The code initializes the offline map screen, sets up the map controller, and retrieves the user’s current location.

Map Rendering and Tile Caching

FlutterMap(
mapController: _mapController,
options: MapOptions(
center: LatLng(16.159146, 73.332974),
zoom: tempMinZoom * 1.0,
maxZoom: tempMaxZoom * 1.0,
// ...
onLongPress: (tapPosition, point) {
_showAddMarkerDialog(context, point);
},
),
children: [
// Base Map
TileLayer(
urlTemplate: urlTemplate,
tileProvider: GetIt.instance<StoreService>().getBaseMapStore != null
? FMTC.instance(baseMapStoreData['storeName']!).getTileProvider(
FMTCTileProviderSettings(
behavior: CacheBehavior.cacheOnly,
// ...
),
)
: NetworkNoRetryTileProvider(),
),
// Bathymetry Layer
if (mapUIBloc.enableBathyMetry == true &&
GetIt.instance<StoreService>().bathymetryLayerStore != null)
TileLayer(
urlTemplate: bathyMapStoreData['sourceURL'],
tileProvider: GetIt.instance<StoreService>().bathymetryLayerStore != null
? FMTC.instance(bathyMapStoreData['storeName']!).getTileProvider(
FMTCTileProviderSettings(
behavior: CacheBehavior.cacheOnly,
// ...
),
)
: NetworkNoRetryTileProvider(),
),
// ...
],
),

The FlutterMap widget is used to render the map with tile caching. It also includes the base map and bathymetry layer, if enabled.

Marker Handling

Future<void> _showAddMarkerDialog(BuildContext context, LatLng position) async {
// ...
}

Future<void> _showEditMarkerDialog(BuildContext context, CustomMarker marker) async {
// ...
}

// ...

// Adding markers to the map
StreamBuilder(
stream: markerService.loadingController,
builder: (context, snapshot) {
return MarkerClusterLayerWidget(
options: MarkerClusterLayerOptions(
// ...
markers: markerService.markers
.map((e) => Marker(
point: e.position,
builder: (context) {
return MarkerWidget(
marker: e,
onEdit: () async {
// ...
},
onDelete: () async {
// ...
},
);
},
))
.toList(),
// ...
),
);
},
),

The code supports adding, editing, and deleting markers on the map.

UI Controls

Positioned(
bottom: 60,
left: 10,
child: StreamBuilder(
stream: mapUIBloc.loadingController,
builder: (context, snapshot) {
return IconButton(
onPressed: () {
mapUIBloc.toggleCustomMarkers();
},
icon: mapUIBloc.enableCustomMarkers
? const Icon(Icons.toggle_on_rounded)
: const Icon(Icons.toggle_off_rounded),
);
},
),
),
// ...

The UI includes toggle buttons for custom markers, grid, and bathymetry layer.

Extra : BaseMap Store Eg.

final Map<String, String> baseMapStoreData = {
"storeName": "baseMap",
"sourceURL":
"https://api.mapbox.com/styles/v1/mapbox/streets-v12/tiles/256/{z}/{x}/{y}?access_token=ACCESS_TOKEN",
"validDuration": "100",
"maxLength": "20000"
};

Conclusion

In the initial part of our exploration, we delved into creating offline maps using Mapbox GL in Flutter. Now, we’ve transitioned to a new chapter using FMTC (Flutter Map Tile Caching). This shift empowers us with different capabilities and tools to master the intricacies of offline maps.

As we conclude this segment, anticipate more insights in the upcoming part of the blog. Our focus will shift towards optimizing download sizes and implementing various strategies for efficient tile fetching. Brace yourself for advanced tips and tricks that will elevate your Flutter offline maps to new heights.

Stay tuned and happy coding!

Ref :
https://fmtc.jaffaketchup.dev/
https://pub.dev/packages/flutter_map_tile_caching
https://www.mapbox.com/
https://www.gebco.net/data_and_products/gridded_bathymetry_data/
https://pub.dev/packages/flutter_map
https://docs.fleaflet.dev/

--

--