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/

Ready to Build Something Amazing?

Whether you need development expertise or technical consultation, I'm here to help bring your ideas to life.