---
name: mapbox-flutter-patterns
description: Official integration patterns for the Mapbox Maps Flutter SDK. Covers installation, iOS/Android platform setup, access token configuration, MapWidget initialization, camera control, annotations with tap handling, user location, and loading GeoJSON. Based on official Mapbox documentation.
---
# Mapbox Flutter Integration Patterns
Official patterns for integrating the Mapbox Maps SDK for Flutter (mapbox_maps_flutter) on iOS and Android with Dart.
**Use this skill when:**
- Installing and configuring mapbox_maps_flutter in a Flutter app
- Setting the Mapbox access token the right way
- Initializing a `MapWidget` with camera / style options
- Adding annotations (points, circles, lines, polygons) and handling taps
- Showing the user location puck
- Loading GeoJSON from app assets
- Troubleshooting iOS build failures after adding Mapbox
**Official Resources:**
- [Flutter Maps Guides](https://docs.mapbox.com/flutter/maps/guides/)
- [API Reference on pub.dev](https://pub.dev/documentation/mapbox_maps_flutter/latest/)
- [Example App](https://github.com/mapbox/mapbox-maps-flutter/tree/main/mapbox_maps_flutter/example)
> Web and desktop are not supported — the Flutter SDK targets iOS and Android only.
---
## Installation & Setup
### Requirements
- Flutter SDK 3.22.3 / Dart 3.4.4+
- **iOS: deployment target 14.0 or higher**
- **Android: minSdk 21 or higher**
- Free Mapbox account
### Step 1: Add the dependency
```yaml
# pubspec.yaml
dependencies:
mapbox_maps_flutter: ^2.0.0
```
```bash
flutter pub get
```
### Step 2: Bump the iOS deployment target to 14.0 (required)
**This is the single most common cause of iOS build failures after adding Mapbox.** The Flutter SDK requires **iOS 14.0** and will not compile on the Flutter default.
1. Open `ios/Runner.xcworkspace` in Xcode.
2. Select the **Runner** target → **General** → set **Minimum Deployments → iOS** to `14.0`.
3. If `ios/Podfile` exists, update the platform line too:
```ruby
# ios/Podfile
platform :ios, '14.0'
```
You do not need to worry about CocoaPods vs Swift Package Manager — `mapbox_maps_flutter` supports both and Flutter picks whichever your app is configured for.
### Step 3: iOS location permission
Add the purpose string to `ios/Runner/Info.plist`:
```xml
NSLocationWhenInUseUsageDescription
Show your location on the map
```
### Step 4: Android permissions
Add to `android/app/src/main/AndroidManifest.xml`:
```xml
```
### Step 5: Configure the access token
The recommended pattern is to pass the token via `--dart-define` at build/run time and set it on `MapboxOptions` before creating any `MapWidget`.
```bash
flutter run --dart-define=ACCESS_TOKEN=pk.your_token_here
```
```dart
// main.dart
import 'package:flutter/material.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';
const accessToken = String.fromEnvironment('ACCESS_TOKEN');
void main() {
MapboxOptions.setAccessToken(accessToken);
runApp(const MaterialApp(home: MapScreen()));
}
```
Never hard-code tokens in source. For CI, pass `--dart-define=ACCESS_TOKEN=$MAPBOX_ACCESS_TOKEN`.
---
## Map Initialization
### Basic map
```dart
import 'package:flutter/material.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';
class MapScreen extends StatelessWidget {
const MapScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: MapWidget(
key: const ValueKey('mapWidget'),
cameraOptions: CameraOptions(
center: Point(coordinates: Position(-122.4194, 37.7749)),
zoom: 12,
),
styleUri: MapboxStyles.STANDARD,
),
);
}
}
```
### Grab the `MapboxMap` controller
```dart
class MapScreen extends StatefulWidget {
const MapScreen({super.key});
@override
State createState() => _MapScreenState();
}
class _MapScreenState extends State {
MapboxMap? mapboxMap;
void _onMapCreated(MapboxMap controller) {
mapboxMap = controller;
}
@override
Widget build(BuildContext context) {
return MapWidget(
key: const ValueKey('mapWidget'),
onMapCreated: _onMapCreated,
cameraOptions: CameraOptions(
center: Point(coordinates: Position(-122.4194, 37.7749)),
zoom: 12,
),
);
}
}
```
---
## Add Annotations
Use `mapboxMap.annotations` to create managers for point, circle, polyline, and polygon annotations. Managers are long-lived — create them once and reuse for updates.
### Point annotations with a custom image
```dart
import 'package:flutter/services.dart' show rootBundle;
PointAnnotationManager? pointAnnotationManager;
Future _addMarkers(MapboxMap mapboxMap) async {
pointAnnotationManager = await mapboxMap.annotations.createPointAnnotationManager();
final bytes = await rootBundle.load('assets/marker.png');
final imageBytes = bytes.buffer.asUint8List();
final options = [
PointAnnotationOptions(
geometry: Point(coordinates: Position(-122.4194, 37.7749)),
image: imageBytes,
iconSize: 1.2,
),
PointAnnotationOptions(
geometry: Point(coordinates: Position(-122.4094, 37.7849)),
image: imageBytes,
),
];
await pointAnnotationManager!.createMulti(options);
}
```
Remember to register the asset in `pubspec.yaml`:
```yaml
flutter:
assets:
- assets/marker.png
```
### Tap handling
Use `manager.tapEvents` — this is the current API. `addOnPointAnnotationClickListener` is deprecated.
`tapEvents` returns a `Cancelable` that you store and invoke `.cancel()` on when the listener is no longer needed:
```dart
final Cancelable tapSubscription = pointAnnotationManager!.tapEvents(
onTap: (annotation) {
debugPrint('Tapped annotation ${annotation.id}');
},
);
@override
void dispose() {
tapSubscription.cancel();
super.dispose();
}
```
The same pattern — returning a `Cancelable` — exists on every manager's `longPressEvents` and `dragEvents`, and across the other annotation types (`CircleAnnotationManager.tapEvents`, etc.).
### Load annotations from GeoJSON
```dart
import 'dart:convert';
import 'package:flutter/services.dart' show rootBundle;
Future _loadGeoJson(MapboxMap mapboxMap) async {
final raw = await rootBundle.loadString('assets/coffee_shops.geojson');
final geo = jsonDecode(raw) as Map;
final features = (geo['features'] as List).cast