Flutter Flow: MapBox SDK with custom markers
Published on January 3, 2025
You can easily add the Flutter Map Box SDK with custom markers. Start with the official documentation, create an account, and create a public access token. Get Started | Maps SDK for Flutter | Mapbox Docs
First create a new custom widget in Flutter Flow and create the following parameters.
I’ve named my custom widget MapBox.
Then add the following code to load images:
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';
import 'dart:ui' as ui;
import 'dart:async';
import 'dart:typed_data';
MapboxMap? mapboxMap;
PointAnnotationManager? pointAnnotationManager;
PointAnnotation? selectedAnnotation;
PlaceStruct? selectedPlace;
Future<Uint8List?> loadImage(String path) async {
if (path.toLowerCase().startsWith('http')) {
return await loadNetworkImage(path);
} else {
return await loadAssetImage(path);
}
}
Future<Uint8List?> loadNetworkImage(String path) async {
final completer = Completer<ImageInfo>();
var image = NetworkImage(path);
image.resolve(const ImageConfiguration()).addListener(ImageStreamListener(
(ImageInfo info, bool _) => completer.complete(info)));
final imageInfo = await completer.future;
final byteData =
await imageInfo.image.toByteData(format: ui.ImageByteFormat.png);
return byteData?.buffer.asUint8List();
}
Future<Uint8List?> loadAssetImage(String path) async {
final completer = Completer<ImageInfo>();
final assetPath =
path.startsWith('assets/images/') ? path : 'assets/images/$path';
var image = AssetImage(assetPath);
image.resolve(const ImageConfiguration()).addListener(ImageStreamListener(
(ImageInfo info, bool _) => completer.complete(info)));
final imageInfo = await completer.future;
final byteData =
await imageInfo.image.toByteData(format: ui.ImageByteFormat.png);
return byteData?.buffer.asUint8List();
}
Now add the code to add the markers on the map
@override
void initState() {
super.initState();
MapboxOptions.setAccessToken(widget.publicAccessToken);
}
_onMapCreated(MapboxMap mapboxMap) async {
this.mapboxMap = mapboxMap;
pointAnnotationManager =
await mapboxMap.annotations.createPointAnnotationManager();
// Add click listener
pointAnnotationManager?.addOnPointAnnotationClickListener(
MarkerClickListener(
onClick: (annotation) {
setState(() {
selectedAnnotation = annotation;
selectedPlace = widget.places.firstWhere(
(place) =>
place.latitude == annotation.geometry.coordinates.lat &&
place.longitude == annotation.geometry.coordinates.lng,
orElse: () => widget.places.first,
);
});
final callback = widget.onClickMarker;
if (callback != null) {
await callback(selectedPlace);
}
},
),
);
await _updateMarkers();
}
@override
void didUpdateWidget(MapBox oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.places != widget.places) {
_updateMarkers();
}
}
Future<void> _updateMarkers() async {
if (pointAnnotationManager != null) {
// Clear existing markers
await pointAnnotationManager?.deleteAll();
// Create new markers for all places
for (var place in widget.places) {
if (place.imageUrl != null) {
final imageData = await loadImage(place.imageUrl!);
if (imageData != null) {
PointAnnotationOptions pointAnnotationOptions =
PointAnnotationOptions(
geometry:
Point(coordinates: Position(place.longitude, place.latitude)),
image: imageData,
iconSize: 1.0,
);
try {
await pointAnnotationManager?.create(pointAnnotationOptions);
} catch (e) {
print('Error creating point annotation: $e');
}
}
}
}
}
}
class MarkerClickListener extends OnPointAnnotationClickListener {
final Function(PointAnnotation) onClick;
MarkerClickListener({required this.onClick});
@override
void onPointAnnotationClick(PointAnnotation annotation) {
onClick(annotation);
}
}
Now update the build method
@override
Widget build(BuildContext context) {
return SizedBox(
width: widget.width,
height: widget.height,
child: MapWidget(
key: ValueKey("mapWidget"),
cameraOptions: CameraOptions(
center: Point(
coordinates: Position(
widget.centerLongitude ?? widget.places.first.longitude,
widget.centerLatitude ?? widget.places.first.latitude,
),
),
zoom: widget.zoom,
),
styleUri: MapboxStyles.LIGHT,
textureView: true,
onMapCreated: _onMapCreated,
),
);
}
}
You can get the entire file here:
// Automatic FlutterFlow imports
import '/backend/backend.dart';
import '/backend/schema/structs/index.dart';
import '/backend/supabase/supabase.dart';
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/widgets/index.dart'; // Imports other custom widgets
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom widget code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';
import 'dart:ui' as ui;
import 'dart:async';
import 'dart:typed_data';
class MapBox extends StatefulWidget {
const MapBox({
super.key,
this.width,
this.height,
required this.places,
required this.zoom,
this.centerLatitude,
this.centerLongitude,
this.onClickMarker,
required this.publicAccessToken,
});
final double? width;
final double? height;
final List<PlaceStruct> places;
final double zoom;
final double? centerLatitude;
final double? centerLongitude;
final Future Function(PlaceStruct? place)? onClickMarker;
final String publicAccessToken;
@override
State<MapBox> createState() => _MapBoxState();
}
class _MapBoxState extends State<MapBox> {
MapboxMap? mapboxMap;
PointAnnotationManager? pointAnnotationManager;
PointAnnotation? selectedAnnotation;
PlaceStruct? selectedPlace;
Future<Uint8List?> loadImage(String path) async {
if (path.toLowerCase().startsWith('http')) {
return await loadNetworkImage(path);
} else {
return await loadAssetImage(path);
}
}
Future<Uint8List?> loadNetworkImage(String path) async {
final completer = Completer<ImageInfo>();
var image = NetworkImage(path);
image.resolve(const ImageConfiguration()).addListener(ImageStreamListener(
(ImageInfo info, bool _) => completer.complete(info)));
final imageInfo = await completer.future;
final byteData =
await imageInfo.image.toByteData(format: ui.ImageByteFormat.png);
return byteData?.buffer.asUint8List();
}
Future<Uint8List?> loadAssetImage(String path) async {
final completer = Completer<ImageInfo>();
final assetPath =
path.startsWith('assets/images/') ? path : 'assets/images/$path';
var image = AssetImage(assetPath);
image.resolve(const ImageConfiguration()).addListener(ImageStreamListener(
(ImageInfo info, bool _) => completer.complete(info)));
final imageInfo = await completer.future;
final byteData =
await imageInfo.image.toByteData(format: ui.ImageByteFormat.png);
return byteData?.buffer.asUint8List();
}
@override
void initState() {
super.initState();
MapboxOptions.setAccessToken(widget.publicAccessToken);
}
_onMapCreated(MapboxMap mapboxMap) async {
this.mapboxMap = mapboxMap;
pointAnnotationManager =
await mapboxMap.annotations.createPointAnnotationManager();
// Add click listener
pointAnnotationManager?.addOnPointAnnotationClickListener(
MarkerClickListener(
onClick: (annotation) {
setState(() {
selectedAnnotation = annotation;
selectedPlace = widget.places.firstWhere(
(place) =>
place.latitude == annotation.geometry.coordinates.lat &&
place.longitude == annotation.geometry.coordinates.lng,
orElse: () => widget.places.first,
);
});
final callback = widget.onClickMarker;
if (callback != null) {
await callback(selectedPlace);
}
},
),
);
// Create markers for all places
await _updateMarkers();
}
@override
void didUpdateWidget(MapBox oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.places != widget.places) {
_updateMarkers();
}
}
Future<void> _updateMarkers() async {
if (pointAnnotationManager != null) {
// Clear existing markers
await pointAnnotationManager?.deleteAll();
// Create new markers for all places
for (var place in widget.places) {
if (place.imageUrl != null) {
final imageData = await loadImage(place.imageUrl!);
if (imageData != null) {
PointAnnotationOptions pointAnnotationOptions =
PointAnnotationOptions(
geometry:
Point(coordinates: Position(place.longitude, place.latitude)),
image: imageData,
iconSize: 1.0,
);
try {
await pointAnnotationManager?.create(pointAnnotationOptions);
} catch (e) {
print('Error creating point annotation: $e');
}
}
}
}
}
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: widget.width,
height: widget.height,
child: MapWidget(
key: ValueKey("mapWidget"),
cameraOptions: CameraOptions(
center: Point(
coordinates: Position(
widget.centerLongitude ?? widget.places.first.longitude,
widget.centerLatitude ?? widget.places.first.latitude,
),
),
zoom: widget.zoom,
),
styleUri: MapboxStyles.LIGHT,
textureView: true,
onMapCreated: _onMapCreated,
),
);
}
}
class MarkerClickListener extends OnPointAnnotationClickListener {
final Function(PointAnnotation) onClick;
MarkerClickListener({required this.onClick});
@override
void onPointAnnotationClick(PointAnnotation annotation) {
onClick(annotation);
}
}
Final Thoughts
Congratulations on adding Map Box to your project.
I have also now added this to the free library on marketplace: FlutterFlow Marketplace
You can generate any widget including maps with custom markers, polylines, polygons, etc at https://codewhims.com/