Skip to main content
For the complete documentation index, see llms.txt.

Migrate to v3

The Mapbox Maps SDK for Flutter v3 adds web support and a more ergonomic API. This guide covers the new features and walks you through the changes you need to make to upgrade your app from v2 to v3.

Your app still imports package:mapbox_maps_flutter/mapbox_maps_flutter.dart, the same as in v2.

Version compatibility​

v2v3
Flutter3.27.0 or higher3.38.1 or higher
Dart3.4.4 or higher3.10.0 or higher
Android minSdk2121
iOS14 or higher14 or higher
WebNot supportedSupported, backed by Mapbox GL JS

1. Update dependencies​

Update your app to use v3 of mapbox_maps_flutter, and make sure your project meets the version compatibility requirements above.

v3 is a federated plugin split into several packages, but your app only depends on mapbox_maps_flutter. It pulls in the Android and iOS implementation, mapbox_maps_flutter_mobile, and the web implementation, mapbox_maps_flutter_web, automatically. Both share types from mapbox_maps_flutter_platform_interface.

Full instructions for access tokens, the dependency, and your first map are in the Get Started guide.

GUIDE
Get Started with Maps SDK for Flutter

Install v3, configure an access token, and add a map on iOS, Android, and web.

2. Explore new features​

2.1 Web support​

Your map code can now run on Flutter web, powered by Mapbox GL JS. The same MapWidget and most APIs work on Android, iOS, and web.

2.2 MapWidget is a StatelessWidget​

MapWidget is now a StatelessWidget that passes its configuration to a platform widget, which holds the map state and renders the map. Set where the map starts with viewport, and move the camera with a ViewportController. Section 3.2 shows how to switch from cameraOptions to viewport.

2.3 ViewportController​

Use a ViewportController to move the camera, for example to fly to a new location. Create the controller, pass it to MapWidget, and call moveTo whenever the camera should change. It replaces setStateWithViewportAnimation. Section 3.2 shows the code before and after.

2.4 Gesture events​

Pan, zoom, rotate, and pitch gestures each have their own gestureEvents stream on mapboxMap.gestures. Listen to a stream to get updates while the user moves the map, and cancel the subscription when you no longer need it. These streams replace the scroll and zoom listeners. Section 3.5 shows how to switch.

On web, you can also listen for camera changes made with the keyboard, such as arrow keys and +/-, through mapboxMap.gestures.keyboard.gestureEvents.

2.5 Style APIs on the map​

You can now call style methods, such as addLayer, directly on MapboxMap and Snapshotter, without going through .style.

// v2
await mapboxMap.style.addLayer(myLayer);

// v3
await mapboxMap.addLayer(myLayer);

Section 3.9 has more examples.

2.6 Indoor API​

The new experimental indoor API lets you show the floors inside a building on Android, iOS, and web. Indoor maps are part of the Mapbox Standard style, but they are disabled by default. To enable them, set the showIndoor configuration property on the basemap style import to true after the style loads. Indoor floor plans only appear for buildings that have indoor map data.

Once indoor maps are enabled, use mapboxMap.indoor to work with floors. Listen to indoorUpdates to find out which floors are available and which one is selected. Call selectFloor to show a different floor, or pass null to leave floor selection.

await mapboxMap.setStyleImportConfigProperty('basemap', 'showIndoor', true);

final subscription = mapboxMap.indoor.indoorUpdates.listen((state) {
print('Selected floor: ${state.selectedFloorId}');
print('Available floors: ${state.floors}');
});

await mapboxMap.indoor.selectFloor(floorId);

To configure the built-in floor selector, use mapboxMap.indoorSelector.

2.7 StyleImage​

StyleImage is a simpler way to add images, such as custom icons, to the map. Create one from a PNG, JPEG, or WebP file with StyleImage.bytes, from raw pixels with StyleImage.rgba, or from a Flutter ui.Image with StyleImage.fromImage. Pass it to addImage or updateImageForSource, and read an image back with getImage.

await mapboxMap.addImage(
'icon',
1.0,
StyleImage.bytes(pngBytes),
);

final image = await mapboxMap.getImage('icon');

StyleImage replaces the MbxImage-based methods. Section 3.10 shows how to switch.

3. Replace deprecated APIs and address breaking changes​

Some v2 APIs are deprecated or removed in v3. Deprecated APIs still work for now, but they will be removed in a future release. Removed APIs cause build errors until you replace them. The sections below show what to use instead.

3.1 Replace getAccessToken() with accessToken​

MapboxOptions.getAccessToken() was removed. Read the token from MapboxOptions.accessToken instead. MapboxOptions.setAccessToken works the same as before.

// v2
final token = await MapboxOptions.getAccessToken();

// v3
final token = await MapboxOptions.accessToken;

3.2 Replace camera setup with viewport and ViewportController​

Replace cameraOptions with viewport​

MapWidget.cameraOptions was removed. Use viewport to set where the map starts.

Replace setStateWithViewportAnimation with ViewportController​

Use a ViewportController to move the camera instead of setStateWithViewportAnimation. Dispose the controller in your widget's dispose method.

// v2
MapWidget(
cameraOptions: CameraOptions(
center: Point(coordinates: Position(-80.1263, 25.7845)),
zoom: 12.0,
),
)

setStateWithViewportAnimation(
() => _viewport = CameraViewportState(center: nyc, zoom: 12),
transition: FlyViewportTransition(duration: Duration(seconds: 2)),
);

// v3
final _viewportController = ViewportController();

MapWidget(
viewport: CameraViewportState(
center: Point(coordinates: Position(-80.1263, 25.7845)),
zoom: 12.0,
),
viewportController: _viewportController,
)

_viewportController.moveTo(
CameraViewportState(center: nyc, zoom: 12),
transition: FlyViewportTransition(duration: Duration(seconds: 2)),
completion: (finished) => print('done: $finished'),
);


void dispose() {
_viewportController.dispose();
super.dispose();
}

See the camera playground example for a full sample.

3.3 Remove null values from coordinatesForPixels input​

coordinatesForPixels now takes a List<ScreenCoordinate> instead of a List<ScreenCoordinate?>. Remove any null entries before you call it.

3.4 Replace tap and long-tap listeners with addInteraction​

The tap and long-tap listeners on MapWidget and MapboxMap were removed. Add an interaction in onMapCreated instead.

// v2
MapWidget(
onTapListener: (context) => print(context.point),
onLongTapListener: (context) => print(context.point),
)

// v3
void _onMapCreated(MapboxMap mapboxMap) {
mapboxMap.addInteraction(
TapInteraction.onMap((context) {
print('tap at ${context.point}');
}),
);
mapboxMap.addInteraction(
LongTapInteraction.onMap((context) {
print('long tap at ${context.point}');
}),
);
}

To respond to taps on specific map features, such as points of interest, buildings, or your own layers, pass a FeaturesetDescriptor to TapInteraction or LongTapInteraction. See the standard style interactions example.

Note

LongTapInteraction is not supported on web yet.

3.5 Replace scroll and zoom listeners with gestureEvents​

Instead of the scroll and zoom listeners on MapWidget and MapboxMap, listen to the gesture event streams.

// v2
MapWidget(
onScrollListener: (context) => print('scroll: ${context.point}'),
onZoomListener: (context) => print('zoom: ${context.point}'),
)

mapboxMap.onMapScrollListener = (context) => print('scroll: ${context.point}');
mapboxMap.onMapZoomListener = (context) => print('zoom: ${context.point}');

// v3
void _onMapCreated(MapboxMap mapboxMap) {
mapboxMap.gestures.pan.gestureEvents.listen((context) {
print('pan: ${context.point}');
});
mapboxMap.gestures.zoom.gestureEvents.listen((context) {
print('zoom: ${context.point}');
});
}

See the gestures example for pan, zoom, rotate, and pitch, including how to stop listening.

3.6 Replace annotation click listeners with tapEvents​

Annotation click listeners, such as addOnPointAnnotationClickListener, were removed. Use tapEvents on the annotation manager instead.

// v2
manager.addOnPointAnnotationClickListener(
(annotation) => print(annotation.id),
);

// v3
manager.tapEvents(onTap: (annotation) {
print(annotation.id);
});

3.7 Replace PointAnnotation.iconImageCrossFade with PointAnnotationManager.setIconImageCrossFade​

The symbol layer iconImageCrossFade property is no longer data-driven, so you can't set it on each annotation. Set it once on the PointAnnotationManager instead. The value applies to all annotations from that manager.

// v2
final annotation = await manager.create(
PointAnnotationOptions(
geometry: point,
iconImageCrossFade: 0.5,
),
);

// v3
final annotation = await manager.create(
PointAnnotationOptions(geometry: point),
);
await manager.setIconImageCrossFade(0.5);

3.8 Replace setCustomHeaders with setCustomHeadersForHost​

setCustomHeaders was removed. It sent your headers, such as access tokens, to every server the map loaded data from, including third-party servers. Use setCustomHeadersForHost to send headers only to the server you choose.

// v2
mapboxMap.httpService.setCustomHeaders({
'Authorization': 'Bearer your_secret_token',
});

// v3
mapboxMap.httpService.setCustomHeadersForHost('tiles.example.com', {
'Authorization': 'Bearer your_secret_token',
});

3.9 Replace MapboxMap.style and Snapshotter.style with direct style methods​

Call style methods directly on MapboxMap and Snapshotter, without .style. The method names are the same.

// v2
await mapboxMap.style.setStyleURI(MapboxStyles.STANDARD);
await mapboxMap.style.addLayer(myLayer);
final layer = await mapboxMap.style.getLayer('my-layer');

await snapshotter.style.setStyleURI(MapboxStyles.LIGHT);

// v3
await mapboxMap.setStyleURI(MapboxStyles.STANDARD);
await mapboxMap.addLayer(myLayer);
final layer = await mapboxMap.getLayer('my-layer');

await snapshotter.setStyleURI(MapboxStyles.LIGHT);

3.10 Replace MbxImage style image methods with StyleImage​

StyleImage makes it easier to pass an image. Use StyleImage.bytes for a PNG, JPEG, or WebP file, StyleImage.rgba for raw pixels, or StyleImage.fromImage for a Flutter ui.Image.

// v2
await mapboxMap.addStyleImage(id, scale, mbxImage);
await mapboxMap.updateStyleImageSourceImage(sourceId, mbxImage);
final image = await mapboxMap.getStyleImage(id);

// v3
await mapboxMap.addImage(id, scale, StyleImage.bytes(pngBytes));
await mapboxMap.updateImageForSource(sourceId, StyleImage.rgba(
width: width,
height: height,
pixels: pixels,
));
final image = await mapboxMap.getImage(id);
Note

getImage and updateImageForSource are not supported on web yet.

3.11 Remove ChangeNotifier usage on MapboxMap​

MapboxMap can no longer be used as a Listenable or ChangeNotifier. In v2, it never sent any notifications, so listeners you added never ran. You can safely remove that code.

3.12 Replace RenderedQueryGeometry.value and type with typed geometries​

RenderedQueryGeometry is now a sealed class with a subclass for each kind of geometry: a point, a box, or a list of points. Instead of checking type and decoding value, use a switch to read the typed data directly. The compiler checks that every case is handled.

// v2
final geometry = RenderedQueryGeometry.fromScreenCoordinate(point);

if (geometry.type == Type.SCREEN_COORDINATE) {
final decoded = jsonDecode(geometry.value);
// ...
}

// v3
final geometry = RenderedQueryGeometry.fromScreenCoordinate(point);

switch (geometry) {
case ScreenCoordinateRenderedQueryGeometry(:final point):
print(point);
case ScreenBoxRenderedQueryGeometry(:final box):
print(box);
case ScreenCoordinateListRenderedQueryGeometry(:final points):
print(points);
}

3.13 Review the new Android display default​

On Android, v3 changes the default platform view mode. The map now uses Hybrid Composition with a SurfaceView, which avoids the per-frame copy that a TextureView needs. v2 used Virtual Display with a TextureView.

To keep the v2 behavior, set MapWidget.androidHostingMode to the mode you need and set MapWidget.textureView to true. A TextureView is also required for a transparent map background.

3.14 Replace getDebug and setDebug with getDebugOptions and setDebugOptions​

Pass setDebugOptions the full list of debug options you want on. Any option that isn't in the list is turned off.

// v2
await mapboxMap.setDebug([
MapDebugOptions(data: MapDebugOptionsData.TILE_BORDERS),
], true);
final options = await mapboxMap.getDebug();

// v3
await mapboxMap.setDebugOptions([
MapWidgetDebugOptions.tileBorders,
MapWidgetDebugOptions.camera,
]);
final options = await mapboxMap.getDebugOptions();
Note

getDebugOptions and setDebugOptions are not supported on web yet.

3.15 Replace renamed types​

Some types have new names. Update any code that refers to them.

v2v3Notes
StyleLayerLayerSubclasses, such as LineLayer, are unchanged.
StyleSourceSourceSubclasses, such as GeoJsonSource, are unchanged.
RequestResourceRequest
ResponseResourceResponse
ErrorResponseError
LocationSettings, *SettingsInterface*SettingsManagerFor example, CompassSettingsInterface is now CompassSettingsManager.
MapDebugOptions / MapDebugOptionsDataMapWidgetDebugOptionsNot supported on web yet.

4. Review other changes​

Common types, such as CameraOptions, CameraState, MapOptions, ViewportState, and the annotation, offline, style, and event types, moved to mapbox_maps_flutter_platform_interface. You can still import them from package:mapbox_maps_flutter/mapbox_maps_flutter.dart as before. You only need a direct dependency on mapbox_maps_flutter_platform_interface if you're building a custom platform implementation.

Geometry types, such as Point, Polygon, LineString, and Feature, now come directly from the turf package, instead of SDK subclasses with the same names. You can keep importing them from mapbox_maps_flutter.

5. Test your app​

Test your app thoroughly after making these changes to make sure everything works as expected on each platform you support.

6. Conclusion​

Following the steps above will help you migrate your app to the latest version of the Mapbox Maps SDK for Flutter. If you run into any issues during the migration process, refer to the Mapbox Maps SDK for Flutter documentation or reach out to the Mapbox support team for help.

Was this page helpful?