alfa_vector_mbtiles

Offline Mapbox Vector Tiles for flutter_map — straight from a local .mbtiles file, no network required.

pub version pub points likes license: MIT platform

alfa_vector_mbtiles is a tiny, dependency-light VectorTileProvider that reads gzipped Mapbox Vector Tiles (MVT/PBF) out of a standard .mbtiles SQLite database and feeds them to vector_map_tiles + flutter_map. The result is a fully rendered, pannable, zoomable vector basemap that works 100% offline — ideal for aviation, maritime, field-work, or any app that has to keep a map alive without connectivity.

It powers the offline charts in the Alfaero flight apps.


Screenshots

⚠️ The images below are placeholders. Drop your real captures over the files in doc/screenshots/ (keep the same filenames) and they will appear here and on the pub.dev gallery automatically.

Day basemap Night basemap Offline
Light vector theme Dark vector theme Tiles read from local .mbtiles

Why vector .mbtiles?

Raster .mbtiles Vector .mbtiles (this package)
File size Large (one PNG per tile/zoom) Small (one PBF describes many zooms)
Re-styling Baked in — re-render to change colors Instant — swap the style/theme at runtime
Zoom sharpness Pixelated on over-zoom Crisp — geometry is re-rendered per zoom
Day/Night/VFR 3× the storage Same file, 3 themes
Offline

You ship one .mbtiles file and render it with as many MapLibre/Mapbox styles as you like.


Features

  • 🛰️ Offline-first — every tile comes from the local SQLite file; zero HTTP requests.
  • 🧩 Drop-in for vector_map_tiles — implements VectorTileProvider, so it composes with MemoryCacheVectorTileProvider, VectorTileLayer, TileProviders, and the whole flutter_map stack.
  • 🗜️ Handles the MBTiles spec for you — gunzips tile_data and applies the TMS row flip (tile_row = 2^z − y − 1) so tiles land in the right place.
  • 📦 Asset bootstrap — copies the bundled .mbtiles asset into the app's databases directory on first run, then memoizes the open Database handle for fast subsequent reads.
  • 🎯 Bounds-checked — out-of-range tile requests throw a typed ProviderException instead of returning garbage, and the renderer transparently falls back to lower zoom levels past maximumZoom.
  • 🪶 Lightweight — pure Dart on top of sqflite; no native plugin to register.

Supported platforms

Platform Status Notes
Android via sqflite
iOS via sqflite
Desktop (Win/macOS/Linux) ⚠️ works with sqflite_common_ffi registered as the database factory
Web sqflite has no web backend

Installation

flutter pub add alfa_vector_mbtiles

This also pulls the peers you'll use in the layer: flutter_map, vector_map_tiles, and latlong2.

Add your .mbtiles as an asset in pubspec.yaml:

flutter:
  assets:
    - assets/basemap.mbtiles

Quick start

// `Theme` is hidden from material because `vector_tile_renderer` exports the
// map render `Theme` we use below.
import 'package:flutter/material.dart' hide Theme;
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:vector_map_tiles/vector_map_tiles.dart';
import 'package:vector_tile_renderer/vector_tile_renderer.dart';
import 'package:alfa_vector_mbtiles/alfa_vector_mbtiles.dart';

class OfflineMap extends StatelessWidget {
  const OfflineMap({super.key, required this.theme});

  /// A `vector_tile_renderer` Theme built from a MapLibre/Mapbox style JSON.
  final Theme theme;

  @override
  Widget build(BuildContext context) {
    return FlutterMap(
      // flutter_map v4 API (the version this package depends on):
      // use `center`/`zoom` (v6+ renamed these to `initialCenter`/`initialZoom`).
      options: MapOptions(
        center: LatLng(-23.5505, -46.6333), // São Paulo
        zoom: 12,
        maxZoom: 18,
      ),
      children: [
        VectorTileLayer(
          theme: theme,
          tileProviders: TileProviders({
            // The source name MUST match the `source` used by your style JSON
            // (e.g. 'openmaptiles').
            'openmaptiles': MemoryCacheVectorTileProvider(
              delegate: AlfaVectorMBTilesProvider(
                mbtilesPath: 'assets/basemap.mbtiles',
                // Max zoom *stored in the file* — not the map's max zoom.
                // The renderer over-zooms past this automatically.
                maximumZoom: 14,
              ),
              maxSizeBytes: 2 * 1024 * 1024, // 2 MB in-memory tile cache
            ),
          }),
        ),
      ],
    );
  }
}

A complete, runnable app (with a bundled sample .mbtiles and an OSM-Bright style) lives in example/.


API reference

AlfaVectorMBTilesProvider

AlfaVectorMBTilesProvider({
  required String mbtilesPath,
  int maximumZoom = 16,
});
Parameter Type Default Description
mbtilesPath String Flutter asset path to the .mbtiles file (e.g. assets/basemap.mbtiles). On first read it is copied into the device's databases directory.
maximumZoom int 16 The highest zoom level present in the file. Requests above it are served by over-zooming lower-level tiles — do not set this to the map widget's maxZoom.

Implements VectorTileProvider from vector_map_tiles, so it can be wrapped by MemoryCacheVectorTileProvider and handed to VectorTileLayer / TileProviders.

ProviderException

Thrown for invalid/out-of-range tile coordinates.

class ProviderException implements Exception {
  final Retryable retryable; // Retryable.retry | Retryable.none
  final String message;
  final int? statusCode;
}

How it works

flutter_map (VectorTileLayer)
   │  asks for tile z/x/y
   ▼
MemoryCacheVectorTileProvider        ← in-memory LRU cache (optional but recommended)
   │  cache miss
   ▼
AlfaVectorMBTilesProvider.provide()
   │  validates z/x/y against 2^z bounds
   ▼
MBTilesUtility.getVectorTileBytes()
   │  1. opens the .mbtiles (copied from asset → databases dir on first call)
   │  2. SELECT tile_data WHERE zoom_level=z AND tile_column=x
   │                            AND tile_row = (2^z − y − 1)   ← TMS flip
   │  3. gunzip(tile_data)  →  raw MVT/PBF bytes
   ▼
vector_tile_renderer  →  painted vector tile

The decoded bytes are standard Mapbox Vector Tiles, so any MapLibre/Mapbox GL style that targets your source layers will render correctly.


Generating a compatible .mbtiles

The file must contain gzipped MVT tiles in the tiles table (the OpenMapTiles / OpenStreetMap schema is the common case). Typical toolchains:

  • tilemaker — OSM .pbf → vector .mbtiles locally.
  • planetiler — fast OSM → vector .mbtiles.
  • tippecanoe — GeoJSON/feature data → vector .mbtiles.
  • Extract a region from an existing planet PMTiles/MBTiles set.

Pair it with an OpenMapTiles-compatible style (e.g. OSM Bright, Positron, Dark Matter) converted to a vector_tile_renderer Theme via ThemeReader.


Performance tips

  • Always wrap the provider in MemoryCacheVectorTileProvider — vector tiles are expensive to decode; caching the decoded geometry avoids re-reading SQLite on every frame.
  • Keep maximumZoom honest: a too-high value makes the renderer ask for tiles that don't exist; a too-low value triggers heavy over-zoom rendering.
  • Ship a region-trimmed .mbtiles, not the planet — startup copies the asset to disk once.
  • For very large files, consider lazy-copying or streaming the asset instead of bundling it.

Troubleshooting

Symptom Likely cause
Blank/empty map, no errors The TileProviders key doesn't match the source id in your style JSON.
Invalid tile coordinates The map requested a tile outside 2^z — usually a wrong maximumZoom.
Tiles shifted/flipped Your .mbtiles is XYZ, not TMS; this package assumes TMS row order (per the MBTiles spec).
FormatException while decoding tile_data isn't gzipped MVT (e.g. it's a raster mbtiles). This package is vector only.
Crashes on desktop Register sqflite_common_ffi as the database factory in main().

Roadmap

  • Optional XYZ (non-flipped) tile order support.
  • Pluggable asset → disk copy strategy (stream large files).
  • Desktop/FFI first-class support out of the box.
  • flutter_map v6+ / vector_map_tiles latest compatibility track.

Contributions welcome — see issues.


Credits

This package builds on the original vector_mbtiles approach for reading vector tiles from MBTiles in flutter_map, repackaged and maintained as alfa_vector_mbtiles for the Alfaero apps. Thanks to the upstream authors and the flutter_map / vector_map_tiles communities.

License

MIT © Levi Costa.

Map data and styles you render are subject to their own licenses (e.g. OpenStreetMap / OpenMapTiles require attribution — keep it visible).