flutter_map_vector_tiles_mbtiles 1.0.0
flutter_map_vector_tiles_mbtiles: ^1.0.0 copied to clipboard
MBTiles support for flutter_map_vector_tiles: render a local .mbtiles archive as a flutter_map vector or raster source, fully offline, with SQLite reads kept off the UI isolate.
ποΈ flutter_map_vector_tiles_mbtiles #
MBTiles archives for
flutter_map_vector_tiles.
Point it at a local .mbtiles file and the map renders from disk β no
network, no tile server, no pre-warming a cache.
MBTiles is the SQLite container QGIS, tilemaker, TileServer GL and the Mapbox tooling all emit, so it is usually what you already have when you export a region.
β¨ Why a separate package? #
So that the 99% who never open an archive pay nothing for it. MBTiles
needs SQLite through dart:ffi; keeping that out of the host package
leaves it dependency-light and web-clean. Everything here is one provider
behind the host's public VectorTileProvider interface β no forked
rendering, no patched pipeline.
π Quick start #
1. Install #
dependencies:
flutter_map: ^8.2.0
flutter_map_vector_tiles: ^2.6.0
flutter_map_vector_tiles_mbtiles: ^1.0.0
That is the whole setup β no native library to add and no build
configuration. package:sqlite3 3.x bundles a prebuilt SQLite through
build hooks, which is also why this
package needs Flutter 3.38 / Dart 3.10 or newer. (The host package
still supports Flutter 3.27; only MBTiles carries the newer floor.)
2. Open the archive #
import 'package:flutter_map_vector_tiles_mbtiles/flutter_map_vector_tiles_mbtiles.dart';
final provider = await MbTilesVectorTileProvider.open(
'${(await getApplicationSupportDirectory()).path}/bavaria.mbtiles',
);
3. Give it to the layer #
If you build the theme yourself, key the provider by the source id your theme's layers reference:
VectorTileLayer(
theme: theme,
tileProviders: TileProviders({'openmaptiles': provider}),
)
More often you want a real style's theme, sprites and attribution, with
only the tiles coming from the archive. Substitute it by source id β
which is also why there is no mbtiles:// URL scheme: an archive's path
is only known at runtime, so no portable style document could name it.
final style = await StyleReader(
uri: 'asset://styles/liberty.json',
resolveProvider: (id) async => id == 'openmaptiles' ? provider : null,
).read();
Returning null falls through to the style's own URL, so a style can mix an
offline vector source with hosted raster imagery. The Style takes
ownership of what you return β style.dispose() closes the archive, so
don't also dispose it yourself.
4. Clean up #
style.dispose(); // β¦or provider.dispose() if you wired it up by hand
βοΈ What open accepts #
| Parameter | Default | Notes |
|---|---|---|
path |
required | Absolute path to the .mbtiles file |
minimumZoom / maximumZoom |
the archive's | Overrides what metadata declares; pass a style source's minzoom/maxzoom to narrow it |
cacheKey |
path + size + mtime | Only set this to share cache entries between providers deliberately |
logger |
Logger.noop() |
Logger.console() to see per-tile failures |
open throws MbTilesException when the file is missing, is not a SQLite
database, or has no tiles table β catch it to show a retry UI. Runtime
tile reads never throw into your code; they are logged and retried.
πΊοΈ Raster archives #
Archives of png/jpg/webp tiles are the same object β the provider is
bytes per coordinate either way. Wire it as a raster source instead:
VectorTileLayer(
theme: style.theme,
tileProviders: style.providers,
rasterSources: {'satellite': RasterTileSource(provider: provider)},
)
Pass tileSize: 256 for 256px archives, or imagery renders one zoom level
too coarse.
π Archive metadata #
provider.metadata exposes the metadata table: name, format,
attribution, bounds, center/centerZoom, plus every row verbatim in
values. Useful for framing the camera on the region the archive actually
covers, and for showing the attribution the data requires β which is not
displayed for you:
final bounds = provider.metadata.bounds;
if (bounds != null) mapController.fitCamera(CameraFit.bounds(bounds: bounds));
Parsing is tolerant: a malformed bounds or a non-numeric minzoom
degrades to null rather than failing the archive.
π§ Behaviour worth knowing #
- TMS rows are flipped for you. MBTiles counts rows from the bottom,
slippy-map tiles from the top. If an archive still renders upside down,
it was written with XYZ rows β check
metadata'sschemekey. - Both schemas work. The flat
tilestable and the deduplicatingmap+imagesview are read through the same query. - Reads run on a dedicated isolate.
package:sqlite3is synchronous and provider loads happen on the UI isolate, so a cold lookup on a large archive would otherwise cost you a frame. - No disk cache. The archive is the local copy, so the host's tile
cache is bypassed in both directions (
cacheBytesToDisk => false). Nothing is copied, and no zero-byte markers accumulate for the coordinates it doesn't cover. - Blobs stay compressed. MBTiles stores
pbftiles gzipped; the host's worker isolate inflates them there rather than on the UI isolate.
π₯οΈ Platform support #
| Platform | Architectures |
|---|---|
| Android | armv7a, aarch64, x86, x64 |
| iOS | arm64 (device + simulator), x64 (simulator) |
| macOS | arm64, x64 |
| Windows | aarch64, x64, x86 |
| Linux | armv7, aarch64, x64, x86, riscv64gc |
| Web | not supported |
SQLite is bundled per-architecture by package:sqlite3's build hook, so
there is nothing to install and nothing to configure.
Web is out by construction: archives are SQLite read through dart:ffi.
open throws UnsupportedError there. For a single-file archive that
does work in a browser, use the host package's
PmTilesVectorTileProvider, which serves PMTiles over HTTP range
requests.
π Troubleshooting #
- Version solving fails on
sqlite3β your Flutter is older than 3.38. SQLite is bundled through build hooks, which need Dart 3.10. - You already depend on
sqlite3_flutter_libsβ drop it. It exists to ship a native SQLite forpackage:sqlite32.x; 3.x bundles its own, and keeping both is redundant. MbTilesException: β¦ is not a readable MBTiles archiveβ the file is not SQLite, or has notilestable or view.sqlite3 file.mbtiles '.schema tiles'confirms it quickly.- Blank map, no errors β pass
logger: const Logger.console()to bothStyleReaderandVectorTileLayer. Most often the id you matched inresolveProvideris not a source id the style declares; the console lists them. - Only part of the map is there β that is the archive's extent.
provider.metadata.boundssays what it covers.
ποΈ Architecture #
See doc/ARCHITECTURE.md for the reader-isolate
protocol, the conditional-import seam that keeps dart:ffi out of a web
compile, and why the sqlite3 constraint is capped below 3.0.0.
π€ Contributing #
Please run dart format ., flutter analyze and flutter test before
submitting. Run flutter test --platform chrome too β it is what proves
the web stub still holds.
π License #
BSD 3-Clause Β© 2026 Jonas Grunau