mapbox_gl_flutter_web 0.1.2
mapbox_gl_flutter_web: ^0.1.2 copied to clipboard
Flutter Web bridge for Mapbox GL JS. Pair with mapbox_maps_flutter on mobile via conditional imports to render the same map on every platform.
mapbox_gl_flutter_web #
Flutter Web bridge for Mapbox GL JS. Renders the same Mapbox style, sources,
layers, and gestures that mapbox_maps_flutter exposes natively on Android
and iOS, but in the browser via dart:js_interop.
mapbox_maps_flutter ships native bindings for Android and iOS only. This
package fills the web gap without forking that plugin: pair it via
conditional imports so the same Dart source compiles and runs on every
platform.
Install #
dependencies:
mapbox_maps_flutter: ^2.20.1 # mobile, official
mapbox_gl_flutter_web: ^0.1.0 # web, this package
Add the Mapbox GL JS script + CSS to your web/index.html:
<link href="https://api.mapbox.com/mapbox-gl-js/v3.7.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.7.0/mapbox-gl.js"></script>
If your CSP is restrictive, allow Mapbox tile / sprite endpoints under
connect-src and the GL JS CDN under script-src / style-src.
Conditional-import pattern #
Define a single app_map.dart in your project that re-exports whichever
package is appropriate:
// lib/services/map/app_map.dart
export 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart'
if (dart.library.js_interop) 'package:mapbox_gl_flutter_web/mapbox_gl_flutter_web.dart';
Then import that file everywhere instead of either underlying package:
import 'package:my_app/services/map/app_map.dart';
void main() {
MapboxOptions.setAccessToken('pk.YOUR_PUBLIC_TOKEN');
runApp(const MyApp());
}
class MyMap extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MapboxMapWidget(
cameraOptions: CameraOptions(
center: Point(coordinates: Position(77.5946, 12.9716)),
zoom: 12,
),
onMapCreated: (MapboxMap map) async {
await map.loadStyleURI(MapboxStyles.STANDARD);
},
);
}
}
Important: use a public Mapbox token (
pk.…). Secret tokens (sk.…) are valid for server-to-server calls but the tile API silently rejects them in the browser, leaving you with a black canvas and no error in the console.
What's covered #
| Feature | Status |
|---|---|
Map widget embedding (HtmlElementView + ResizeObserver) |
✅ |
Camera: flyTo, easeTo, getCameraState, setBounds, getBounds |
✅ |
pixelForCoordinate / coordinateForPixel |
✅ |
cameraForCoordinatesPadding (bounds-based on web) |
✅ |
| GeoJSON sources with clustering + cluster properties | ✅ |
| Symbol + Line layers (icon, text, paint properties) | ✅ |
Runtime setStyleLayerProperty (layout vs paint routed automatically) |
✅ |
Style image registration from ui.Image (raw RGBA) |
✅ |
queryRenderedFeatures |
✅ |
setStyleImportConfigProperty (Mapbox Standard lightPreset etc.) |
✅ |
| Scale bar / compass / gesture toggles | ✅ |
triggerRepaint |
✅ |
| Annotations API | ❌ (PRs welcome) |
| Location puck | ❌ |
| Terrain / fog / atmosphere | ❌ |
| Raster, raster-DEM, 3D model layers | ❌ |
| Vector tile sources beyond the style URI | ❌ |
If you need any of the unchecked items, file an issue (or a PR — the
JS-interop pattern in lib/src/impl.dart is straight-line
mechanical).
Gotchas #
-
Container layout timing —
new mapboxgl.Map({})locks the canvas at 400×300 if the container has no layout yet. We defer construction by two frames and attach aResizeObserverto callmap.resize(). If you wrap the widget in something that hides it (display: none) and then shows it, you may need to calltriggerRepaintafter the show. -
CSP. mapbox-gl-js needs
connect-srcaccess tohttps://api.mapbox.com(sprites, tiles, models), plus your local dev origin for hot-reload. -
Custom headers on tiled sources aren't wired up; this package only exposes the subset of the API the SpeakX app actually uses.
-
Wasm dry-run.
dart:js_interopis wasm-compatible, butdart:js_interop_unsafe(used for property access) currently is not. Wasm builds will fail until upstream lands. Compile tojsfor now.
Why not just contribute to mapbox_maps_flutter? #
This package was originally written inside SpeakX to ship the web build of a production Flutter app. We considered upstreaming, but the official plugin re-generates its API surface from a shared cross-platform schema and the maintainer team has been clear web isn't on the official roadmap. Publishing this as a separate community plugin lets other Flutter Web teams skip the JS-interop boilerplate without us signing up to maintain a fork.
If mapbox_maps_flutter ever ships official web support, this package will
become a one-line shim — or you can switch off it entirely without changing
your call sites (because the public type names match).
License #
MIT. See LICENSE.