mapbox-flutter-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Mapbox Flutter Integration Patterns

Mapbox Flutter 集成模式

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:
Web and desktop are not supported — the Flutter SDK targets iOS and Android only.

Mapbox Maps Flutter SDK(mapbox_maps_flutter)在iOS和Android平台上的官方Dart集成方案。
适用场景:
  • 在Flutter应用中安装和配置mapbox_maps_flutter
  • 正确设置Mapbox访问令牌
  • 结合相机/样式选项初始化
    MapWidget
  • 添加标注(点、圆形、线条、多边形)并处理点击事件
  • 显示用户定位图标
  • 从应用资源中加载GeoJSON
  • 解决添加Mapbox后iOS构建失败的问题
官方资源:
Web和桌面端暂不支持——该Flutter SDK仅面向iOS和Android平台。

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
  • Flutter SDK 3.22.3 / Dart 3.4.4+
  • iOS:部署目标版本14.0或更高
  • Android:minSdk 21或更高
  • 免费Mapbox账号

Step 1: Add the dependency

步骤1:添加依赖

yaml
undefined
yaml
undefined

pubspec.yaml

pubspec.yaml

dependencies: mapbox_maps_flutter: ^2.0.0

```bash
flutter pub get
dependencies: mapbox_maps_flutter: ^2.0.0

```bash
flutter pub get

Step 2: Bump the iOS deployment target to 14.0 (required)

步骤2:将iOS部署目标版本提升至14.0(必填)

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.
这是添加Mapbox后iOS构建失败最常见的原因。 Flutter SDK要求最低iOS版本为14.0,无法在Flutter默认版本上编译。
  1. 在Xcode中打开
    ios/Runner.xcworkspace
  2. 选择Runner目标 → 通用 → 将最低部署版本 → iOS设置为
    14.0
  3. 如果项目存在
    ios/Podfile
    ,同时更新其中的平台配置行:
    ruby
    # ios/Podfile
    platform :ios, '14.0'
无需担心CocoaPods与Swift Package Manager的兼容问题——
mapbox_maps_flutter
同时支持两者,Flutter会自动适配应用当前的配置。

Step 3: iOS location permission

步骤3:iOS定位权限配置

Add the purpose string to
ios/Runner/Info.plist
:
xml
<key>NSLocationWhenInUseUsageDescription</key>
<string>Show your location on the map</string>
ios/Runner/Info.plist
中添加权限说明字符串:
xml
<key>NSLocationWhenInUseUsageDescription</key>
<string>在地图上显示您的位置</string>

Step 4: Android permissions

步骤4:Android权限配置

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" />
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

步骤5:配置访问令牌

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
.

推荐的方式是在构建/运行时通过
--dart-define
传递令牌,并在创建任何
MapWidget
前将其设置到
MapboxOptions
中。
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()));
}
切勿在源代码中硬编码令牌。对于CI环境,使用
--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});

  
  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,
      ),
    );
  }
}
dart
import 'package:flutter/material.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';

class MapScreen extends StatelessWidget {
  const MapScreen({super.key});

  
  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

获取
MapboxMap
控制器

dart
class MapScreen extends StatefulWidget {
  const MapScreen({super.key});

  
  State<MapScreen> createState() => _MapScreenState();
}

class _MapScreenState extends State<MapScreen> {
  MapboxMap? mapboxMap;

  void _onMapCreated(MapboxMap controller) {
    mapboxMap = controller;
  }

  
  Widget build(BuildContext context) {
    return MapWidget(
      key: const ValueKey('mapWidget'),
      onMapCreated: _onMapCreated,
      cameraOptions: CameraOptions(
        center: Point(coordinates: Position(-122.4194, 37.7749)),
        zoom: 12,
      ),
    );
  }
}

dart
class MapScreen extends StatefulWidget {
  const MapScreen({super.key});

  
  State<MapScreen> createState() => _MapScreenState();
}

class _MapScreenState extends State<MapScreen> {
  MapboxMap? mapboxMap;

  void _onMapCreated(MapboxMap controller) {
    mapboxMap = controller;
  }

  
  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.
使用
mapboxMap.annotations
创建点、圆形、折线和多边形标注的管理器。管理器为长生命周期对象——创建一次即可重复用于更新操作。

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
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);
}
记得在
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}');
  },
);


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.).
使用
manager.tapEvents
——这是当前的标准API。
addOnPointAnnotationClickListener
已被弃用。
tapEvents
会返回一个
Cancelable
对象,需保存该对象并在不再需要监听器时调用
.cancel()
dart
final Cancelable tapSubscription = pointAnnotationManager!.tapEvents(
  onTap: (annotation) {
    debugPrint('点击了标注 ${annotation.id}');
  },
);


void dispose() {
  tapSubscription.cancel();
  super.dispose();
}
相同的模式——返回
Cancelable
对象——适用于所有管理器的
longPressEvents
dragEvents
,以及其他标注类型(如
CircleAnnotationManager.tapEvents
等)。

Load annotations from GeoJSON

从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.

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);
}
如果有成千上万个要素,建议使用样式图层(
GeoJsonSource
+
SymbolLayer
)替代标注。

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(),
  ),
));

在启用定位图标前,必须已获取权限(可使用
permission_handler
等库)。
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),
);

dart
// 瞬间跳转
await mapboxMap.setCamera(CameraOptions(
  center: Point(coordinates: Position(-80.1263, 25.7845)),
  zoom: 14,
));

// 动画式飞行跳转
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"

iOS构建失败,提示“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
.
Flutter默认的iOS部署目标版本低于Mapbox的最低要求(iOS 14)。在Xcode的Runner目标中将最低部署版本 → iOS设置为
14.0
。如果项目存在
ios/Podfile
,同时设置
platform :ios, '14.0'
并重新运行
pod install

setAccessToken
not called

未调用
setAccessToken

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
.
如果在创建
MapWidget
前忘记调用
MapboxOptions.setAccessToken
,地图会加载为空白网格。务必在
main()
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.
确保使用
manager.tapEvents(onTap: ...)
——
addOnPointAnnotationClickListener
已被弃用。同时确认在创建标注管理器前,已通过
onMapCreated
获取到
MapboxMap
控制器。

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.

iOS/Android不会在热重载时重新读取清单文件或Info.plist。修改权限后需完全重启应用。

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.

  • references/annotations.md
    —— 圆形、折线、多边形标注模式以及GeoJSON源/图层实现方案。
  • references/platform-setup.md
    —— 更深入的iOS/Android配置、令牌策略、发布签名说明。

Additional Resources

额外资源