Embed interactive maps in a Flutter app

Shows how to wire Mapbox maps into Flutter with platform setup, camera controls, tap-able annotations, user location, and GeoJSON layers. Follows official Mapbox patterns.

Best for: Flutter engineers building location-aware or mapping features without Mapbox documentation overhead.

Engineering / planning-thinkingatomicfor-engineersneeds-integrationfrom-repo

Source

Creator's repository · mapbox/mapbox-agent-skills

View on GitHub

License: MIT

Skill file

Preview skill file
---
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/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
<key>NSLocationWhenInUseUsageDescription</key>
<string>Show your location on the map</string>
```

### Step 4: Android permissions

Add to `android/app/src/main/AndroidManifest.xml`:

```xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
```

### 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<MapScreen> createState() => _MapScreenState();
}

class _MapScreenState extends State<MapScreen> {
  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<void> _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>[
    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<void> _loadGeoJson(MapboxMap mapboxMap) async {
  final raw = await rootBundle.loadString('assets/coffee_shops.geojson');
  final geo = jsonDecode(raw) as Map<String, dynamic>;
  final features = (geo['features'] as List).cast<Map<String, dynamic>>();

  final manager = await mapboxMap.annotations.createPointAnnotationManager();
  final icon = (await rootBundle.load('assets/coffee.png')).buffer.asUint8List();

  final options = features.map((feature) {
    final coords = feature['geometry']['coordinates'] as List;
    return PointAnnotationOptions(
      geometry: Point(coordinates: Position(coords[0] as double, coords[1] as double)),
      image: icon,
    );
  }).toList();

  await manager.createMulti(options);
}
```

For thousands of features use a style layer (`GeoJsonSource` + `SymbolLayer`) instead of annotations.

---

## Show User Location

Permissions must already be granted (use `permission_handler` or similar) before enabling the puck.

```dart
await mapboxMap.location.updateSettings(LocationComponentSettings(
  enabled: true,
  puckBearingEnabled: true,
  locationPuck: LocationPuck(
    locationPuck2D: DefaultLocationPuck2D(),
  ),
));
```

---

## Camera Control

```dart
// Instant jump
await mapboxMap.setCamera(CameraOptions(
  center: Point(coordinates: Position(-80.1263, 25.7845)),
  zoom: 14,
));

// Animated fly-to
await mapboxMap.flyTo(
  CameraOptions(
    center: Point(coordinates: Position(-80.1263, 25.7845)),
    zoom: 17,
    bearing: 180,
    pitch: 30,
  ),
  MapAnimationOptions(duration: 2000),
);
```

---

## Troubleshooting

### iOS build fails with "platform is lower than deployment target"

The Flutter default iOS deployment target is lower than Mapbox's minimum (iOS 14). Set **Minimum Deployments → iOS** to `14.0` on the Runner target in Xcode. If the project has an `ios/Podfile`, also set `platform :ios, '14.0'` there and re-run `pod install`.

### `setAccessToken` not called

If you forget to call `MapboxOptions.setAccessToken` before creating a `MapWidget`, the map will load with a blank grid. Always call it in `main()` before `runApp`.

### Annotation tap handler not firing

Make sure you're using `manager.tapEvents(onTap: ...)` — `addOnPointAnnotationClickListener` is deprecated. Also confirm the `MapboxMap` controller is captured via `onMapCreated` before you create the annotation manager.

### Hot reload after permissions change

iOS/Android will not re-read manifests or Info.plist on hot reload. Fully restart the app after editing permissions.

---

## Reference Files

- **`references/annotations.md`** — Circle, Polyline, Polygon patterns and GeoJSON source/layer recipes.
- **`references/platform-setup.md`** — Deeper iOS/Android setup, token strategies, release signing notes.

---

## Additional Resources

- [Flutter Maps Guides](https://docs.mapbox.com/flutter/maps/guides/)
- [Markers and Annotations guide](https://docs.mapbox.com/flutter/maps/guides/markers-and-annotations/)
- [User Location guide](https://docs.mapbox.com/flutter/maps/guides/user-location/)
- [Example App](https://github.com/mapbox/mapbox-maps-flutter/tree/main/example)