Flutter
Flutter
Flutter Flow: MapBox SDK with custom markers
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/