Skip to content

Add Image Markers in Flutter

This tutorial shows how to use custom images as map markers instead of the default pin icons.

Prerequisites

Before you begin, ensure you have:

Using Asset Images

First, add your marker image to your project's assets/images/ folder and register it in pubspec.yaml:

yaml
flutter:
  assets:
    - assets/images/

Then use BitmapDescriptor.fromAssetImage to load it:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class ImageMarkerScreen extends StatefulWidget {
  @override
  _ImageMarkerScreenState createState() => _ImageMarkerScreenState();
}

class _ImageMarkerScreenState extends State<ImageMarkerScreen> {
  MapMetricsController? mapController;
  Set<Marker> markers = {};

  @override
  void initState() {
    super.initState();
    _loadMarkers();
  }

  Future<void> _loadMarkers() async {
    final BitmapDescriptor customIcon = await BitmapDescriptor.fromAssetImage(
      ImageConfiguration(size: Size(48, 48)),
      'assets/images/custom_pin.png',
    );

    setState(() {
      markers = {
        Marker(
          markerId: MarkerId('cafe'),
          position: LatLng(48.8566, 2.3522),
          icon: customIcon,
          infoWindow: InfoWindow(
            title: 'My Favorite Café',
            snippet: 'Best coffee in Paris',
          ),
        ),
      };
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Image Markers')),
      body: MapMetrics(
        styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
        onMapCreated: (controller) => mapController = controller,
        initialCameraPosition: CameraPosition(
          target: LatLng(48.8566, 2.3522),
          zoom: 14.0,
        ),
        markers: markers,
      ),
    );
  }
}

Multiple Image Markers from Data

Load several markers with different custom icons from a data list:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class MultiImageMarkersScreen extends StatefulWidget {
  @override
  _MultiImageMarkersScreenState createState() => _MultiImageMarkersScreenState();
}

class _MultiImageMarkersScreenState extends State<MultiImageMarkersScreen> {
  MapMetricsController? mapController;
  Set<Marker> markers = {};

  final List<Map<String, dynamic>> places = [
    {
      'id': 'restaurant',
      'name': 'Le Bistrot',
      'snippet': 'French restaurant',
      'position': LatLng(48.8580, 2.3500),
      'icon': 'assets/images/restaurant_pin.png',
    },
    {
      'id': 'hotel',
      'name': 'Grand Hotel',
      'snippet': '5-star hotel',
      'position': LatLng(48.8550, 2.3480),
      'icon': 'assets/images/hotel_pin.png',
    },
    {
      'id': 'museum',
      'name': 'Art Gallery',
      'snippet': 'Modern art museum',
      'position': LatLng(48.8540, 2.3550),
      'icon': 'assets/images/museum_pin.png',
    },
  ];

  @override
  void initState() {
    super.initState();
    _loadMarkers();
  }

  Future<void> _loadMarkers() async {
    final Set<Marker> loadedMarkers = {};

    for (final place in places) {
      final icon = await BitmapDescriptor.fromAssetImage(
        ImageConfiguration(size: Size(48, 48)),
        place['icon'],
      );

      loadedMarkers.add(
        Marker(
          markerId: MarkerId(place['id']),
          position: place['position'],
          icon: icon,
          infoWindow: InfoWindow(
            title: place['name'],
            snippet: place['snippet'],
          ),
        ),
      );
    }

    setState(() {
      markers = loadedMarkers;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Places in Paris')),
      body: MapMetrics(
        styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
        onMapCreated: (controller) => mapController = controller,
        initialCameraPosition: CameraPosition(
          target: LatLng(48.8560, 2.3510),
          zoom: 15.0,
        ),
        markers: markers,
      ),
    );
  }
}

Using Network Images

Load marker icons from a URL:

dart
Future<void> _loadNetworkMarker() async {
  final BitmapDescriptor networkIcon = await BitmapDescriptor.fromNetworkImage(
    ImageConfiguration(size: Size(48, 48)),
    'https://example.com/marker-icon.png',
  );

  setState(() {
    markers.add(
      Marker(
        markerId: MarkerId('network_marker'),
        position: LatLng(48.8600, 2.3400),
        icon: networkIcon,
        infoWindow: InfoWindow(title: 'Network Icon'),
      ),
    );
  });
}

Create Markers from Widgets

For fully custom markers, you can create a BitmapDescriptor from a Flutter widget:

dart
Future<BitmapDescriptor> _createWidgetMarker(String label, Color color) async {
  return await BitmapDescriptor.fromWidget(
    Container(
      padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
      decoration: BoxDecoration(
        color: color,
        borderRadius: BorderRadius.circular(20),
        boxShadow: [
          BoxShadow(color: Colors.black26, blurRadius: 4, offset: Offset(0, 2)),
        ],
      ),
      child: Text(
        label,
        style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12),
      ),
    ),
  );
}

// Usage
final icon = await _createWidgetMarker('Café', Colors.brown);

Image Marker Best Practices

TipDetails
SizeKeep images small (48x48 to 96x96 px) for performance
FormatUse PNG with transparency for best results
ResolutionProvide 2x/3x variants for high-DPI screens
LoadingLoad icons in initState — they are async operations
CachingStore loaded BitmapDescriptor objects to avoid reloading

Next Steps


Tip: Always load your BitmapDescriptor icons asynchronously in initState and call setState when they are ready. Trying to create them synchronously in the build method will cause errors.