Skip to content

Migrate from mapbox_maps_flutter

MapLibre started as a fork of the open source Mapbox SDKs. The style spec, sources, layers and expressions are close to those of Mapbox, so styles and layer code carry over with few changes. The main differences are where the map data comes from and how the Flutter API is shaped: maplibre takes map options and annotations as widget parameters and has typed style layers, while mapbox_maps_flutter configures the map through its MapboxMap object and annotation managers.

1. Choose map data

Mapbox styles (mapbox://styles/…) and the Mapbox Standard style need Mapbox services and don't work with MapLibre. Use the style of a tile provider that serves MapLibre styles, such as those in awesome-maplibre, or host your own tiles, for example as a PMTiles file. Your own styles from Mapbox Studio work if you replace their mapbox:// sources, sprites and glyphs with your own URLs.

2. Replace the dependency

flutter pub remove mapbox_maps_flutter
flutter pub add maplibre:^0.4.0-pre.2

Remove MapboxOptions.setAccessToken, the access token from your build configuration, and the Mapbox Maven repository from the Android Gradle files. Follow Platform setup for the platforms of your app. If your tile provider needs a key, it is usually part of the style URL; MapLibreNetwork adds headers to requests if it needs them there.

3. The map widget

MapWidget(
  styleUri: MapboxStyles.OUTDOORS,
  cameraOptions: CameraOptions(
    center: Point(coordinates: Position(9.17, 47.68)),
    zoom: 7,
  ),
  onMapCreated: (mapboxMap) {
    _mapboxMap = mapboxMap;
    mapboxMap.gestures.updateSettings(GesturesSettings(rotateEnabled: false));
    mapboxMap.scaleBar.updateSettings(ScaleBarSettings(enabled: true));
  },
  onStyleLoadedListener: (_) => _addLayers(),
  onTapListener: (context) => print(context.point),
)
MapLibreMap(
  options: MapOptions(
    style: MapStyle.url('https://tiles.openfreemap.org/styles/liberty'),
    initCamera: .position(
      center: Geographic(lon: 9.17, lat: 47.68),
      zoom: 7,
    ),
    movements: MapMovements(rotate: false),
  ),
  onMapCreated: (controller) => _controller = controller,
  onStyleLoaded: (style) => _addLayers(style),
  onClick: (event) => print(event.point),
  children: const [MapScalebar()],
)

maplibre uses geobase types: Geographic(lon: …, lat: …) for a coordinate and Point, LineString or Polygon for geometries. Both packages have a Point, Position and CameraOptions-like types, so don't import both in the same file.

mapbox_maps_flutter maplibre
MapWidget.styleUri, style.setStyleURI MapOptions.style, changed with setState()
MapWidget.cameraOptions MapOptions.initCamera
MapboxMap MapController from onMapCreated
mapboxMap.style StyleController from onStyleLoaded or controller.style
setBounds(CameraBoundsOptions(...)) MapOptions.minZoom, maxZoom, minPitch, maxPitch, maxBounds
gestures.updateSettings(GesturesSettings(...)) MapOptions.movements and MapOptions.gestures
compass, scaleBar, attribution settings MapCompass, MapScalebar, SourceAttribution in children
location.updateSettings(LocationComponentSettings(enabled: true)) UserLocationLayer in children, see User Location
onTapListener, onLongTapListener onClick, onLongClick
onCameraChangeListener, onMapIdleListener onCameraMove, onIdle
onStyleLoadedListener onStyleLoaded
onMapLoadErrorListener onError

4. Camera

mapboxMap.setCamera(CameraOptions(zoom: 10));
mapboxMap.flyTo(
  CameraOptions(center: Point(coordinates: Position(9.17, 47.68)), zoom: 12),
  MapAnimationOptions(duration: 2000),
);
final camera = await mapboxMap.cameraForCoordinateBounds(bounds, padding, null, null, null, null);
await controller.moveCamera(const CameraUpdate.position(zoom: 10));
await controller.animateCamera(
  const CameraUpdate.position(center: Geographic(lon: 9.17, lat: 47.68), zoom: 12),
  animation: const CameraAnimation.fly(duration: Duration(seconds: 2)),
);
final camera = await controller.cameraFor(
  CameraUpdate.bounds(box, padding: const EdgeInsets.all(32)),
);

easeTo is animateCamera with CameraAnimation.ease, the default. getCamera() and the camera getter return the camera without a Future. See Map & Camera.

5. Annotations

mapbox_maps_flutter creates annotations through managers. In maplibre, annotation layers are parameters of MapLibreMap and update with setState():

final manager = await mapboxMap.annotations.createCircleAnnotationManager();
await manager.create(
  CircleAnnotationOptions(
    geometry: Point(coordinates: Position(9.17, 47.68)),
    circleRadius: 12,
    circleColor: Colors.blue.toARGB32(),
  ),
);
MapLibreMap(
  layers: [
    CircleLayer(
      points: const [Feature(geometry: Point(Geographic(lon: 9.17, lat: 47.68)))],
      radius: const .literal(12),
      color: const .literal(Colors.blue),
    ),
  ],
)
mapbox_maps_flutter maplibre
PointAnnotationManager MarkerLayer, or WidgetLayer for Flutter widgets
CircleAnnotationManager CircleLayer
PolylineAnnotationManager PolylineLayer
PolygonAnnotationManager PolygonLayer
tapEvents, dragEvents of a manager onClick, onEnter, onLeave and draggable: true with onDragEnd on the layer, see Click and Hover and Drag
View annotations WidgetLayer

6. Sources and style layers

Sources and layers work the same way. In maplibre they are typed: sources have a named constructor for their input, and layer properties take an Expression instead of a value or a JSON list.

await mapboxMap.style.addSource(GeoJsonSource(id: 'route', data: routeJson));
await mapboxMap.style.addLayer(
  LineLayer(
    id: 'route',
    sourceId: 'route',
    lineColor: Colors.red.toARGB32(),
    lineWidthExpression: ['interpolate', ['linear'], ['zoom'], 10, 2, 16, 8],
  ),
);
await mapboxMap.style.setStyleLayerProperty('route', 'visibility', 'none');
await style.sources.add(GeoJsonSource.data(id: 'route', data: .text(routeJson)));
await style.layers.add(
  LineStyleLayer(
    id: 'route',
    sourceId: 'route',
    color: const .literal(Colors.red),
    width: .interpolate(.linear(), .zoom(), {10: .literal(2), 16: .literal(8)}),
  ),
);
style.layers['route']?.visible = false;

Expression.raw takes the JSON form of an expression, so expressions of an existing style can be copied as they are. See Sources, Style Layers and Expressions.

mapbox_maps_flutter maplibre
style.addSource, removeStyleSource style.sources.add, style.sources.remove
GeoJsonSource.updateGeoJSON style.sources.updateGeoJsonData
style.addLayer, addLayerAt, removeStyleLayer, moveStyleLayer style.layers.add(layer, belowLayerId: …), remove, move
setStyleLayerProperty The setters of the layer from style.layers[id]
style.addStyleImage style.addImage and the other addImage* methods
queryRenderedFeatures, querySourceFeatures controller.queryRenderedFeatures, controller.querySourceFeatures
getGeoJsonClusterLeaves, getGeoJsonClusterChildren, getGeoJsonClusterExpansionZoom controller.getClusterLeaves, getClusterChildren, getClusterExpansionZoom
setFeatureState Not available yet (#646)

7. Offline

OfflineManager downloads regions of a style for offline use, by bounds or by a geometry such as a route. It replaces TileStore and the offline manager of Mapbox, but not on web. See Offline & Cache.

8. What maplibre doesn't have

  • Mapbox data and services: the Mapbox Standard style, search, navigation and the Mapbox terrain and satellite tiles. Use other providers for these.
  • 3D terrain, sky and 3D models (#657).
  • The globe projection, except on web (#602).

What maplibre adds

  • macOS, Windows and Linux.
  • Any tile provider or your own tiles. Tokens and fees depend only on the provider you choose.
  • Declarative annotation layers and Flutter widgets on the map.
  • Typed style layers and expressions.