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:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
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
| Tip | Details |
|---|---|
| Size | Keep images small (48x48 to 96x96 px) for performance |
| Format | Use PNG with transparency for best results |
| Resolution | Provide 2x/3x variants for high-DPI screens |
| Loading | Load icons in initState — they are async operations |
| Caching | Store loaded BitmapDescriptor objects to avoid reloading |
Next Steps
- Markers and Annotations — Default markers with color options
- Draggable Marker — Make image markers draggable
- Add a Popup — Show popups on image marker taps
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.