flutter_map_vector_tiles_mbtiles 1.0.0 copy "flutter_map_vector_tiles_mbtiles: ^1.0.0" to clipboard
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 #

pub package flutter_map license

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's scheme key.
  • Both schemas work. The flat tiles table and the deduplicating map + images view are read through the same query.
  • Reads run on a dedicated isolate. package:sqlite3 is 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 pbf tiles 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 for package:sqlite3 2.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 no tiles table or view. sqlite3 file.mbtiles '.schema tiles' confirms it quickly.
  • Blank map, no errors β†’ pass logger: const Logger.console() to both StyleReader and VectorTileLayer. Most often the id you matched in resolveProvider is 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.bounds says 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

0
likes
160
points
64
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

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.

Repository (GitHub)
View/report issues

Topics

#flutter-map #vector-tiles #mbtiles #offline #maplibre

License

BSD-3-Clause (license)

Dependencies

flutter, flutter_map, flutter_map_vector_tiles, latlong2, sqlite3

More

Packages that depend on flutter_map_vector_tiles_mbtiles