Add Custom Marker Images to your Google Maps in Flutter
In this quick tutorial I demonstrate how to apply custom markers to your own Google Maps in Flutter.
Note: This post assumes you already have the maps set up in your project using the Google Maps Flutter Package, as well as have your own Google Maps API key. If not, follow this link on how to set up your Flutter project to work with Google Maps. Github project for this tutorial here.
If you’re like me, you want to have some level of customization when it comes to creating your own Flutter apps — and specially using common components such as Google Maps. In this particular case, I want to change the default pin that comes with Google Maps for something more custom, such as the image below:
I made this image a transparent PNG, 106x106 pixels (you can make it any size you want but it is always good to optimize it for performance, i.e. if you’re adding lots of pins on your map, performing frequent updates to locations, removing / adding pins dynamically, etc.). I felt this size was decent enough so that when displayed on the map it looked crisp enough.
Make sure your image is added to your project (in my case, it’s at the root of the assets folder, under assets/destination_map_marker.png) and ensure your pubspec.yaml is configured so that it pulls the images correctly.
My map page is defined as a StatefulWidget:
// make sure package is imported
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:flutter/material.dart';
import 'dart:async';class MapPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => MapPageState();
}class MapPageState extends State<MapPage> {}
In my MapPageState page, I defined a BitmapDescriptor variable, which I call pinLocationIcon. I also create a a variable to hold the markers i’ll assign to the map called _markers. Assuming your Google Map is configured using the google_maps_flutter package, you will need a GoogleMapController as well.
Then I proceed to override the initState() method, in which I proceed to set up my pinLocation using by using the BitmapDescriptor’s fromAssetImage method.
class MapPageState extends State<MapPage> { BitmapDescriptor pinLocationIcon;
Set<Marker> _markers = {};
Completer<GoogleMapController> _controller = Completer(); @override
void initState() {
BitmapDescriptor.fromAssetImage(
ImageConfiguration(devicePixelRatio: 2.5),
'assets/destination_map_marker.png').then((onValue) {
pinLocationIcon = onValue;
});
}
}
BitmapDescriptor’s fromAssetImage returns a Future (the result of an asynchronous operation, hence the need to use .then() to capture the value once it’s available); in our case we are fetching the physical image asset asynchronously upfront, then assigning it to our local pinLocationIcon variable so we can use it when needed;
If you’re not a fan of using .then() approach, you could do the async / await approach, and instead do the following:
class MapPageState extends State<MapPage> { BitmapDescriptor pinLocationIcon; @override
void initState() {
super.initState();
setCustomMapPin();
} void setCustomMapPin() async {
pinLocationIcon = await BitmapDescriptor.fromAssetImage(
ImageConfiguration(devicePixelRatio: 2.5),
'assets/destination_map_marker.png');
}
...
}
The BitmapDescriptor’s fromAssetImage takes two parameters:
-ImageConfiguration, which you use to define its size and device pixel ratio; for me, the magic value was 2.5 and the pin shows well in all devices I tested it. This worked much better than the size parameter.
-The path to the marker inside your assets folder (in my case assets/destination_map_marker.png).
Still inside the MapPageState, proceed to override the build method, and in this method, I set the location (based on lat and long coordinates) I’ll be using for both the Camera location and the pin location.
@override
Widget build(BuildContext context) { LatLng pinPosition = LatLng(37.3797536, -122.1017334);
// these are the minimum required values to set
// the camera position
CameraPosition initialLocation = CameraPosition(
zoom: 16,
bearing: 30,
target: pinPosition
);
}
Then in your map code (in my case, I’m placing it full screen on my page, upon the map getting created, I handle the appropriate event, and at this point I add my Marker object, add it to the list of Markers available within the setState() method so it notifies Flutter to update the map with the newly added marker, and then assign both its position and my custom BitmapDescriptor object as its icon property, as follows:
@override
Widget build(BuildContext context) {... (rest of the code omitted for brevity)...return GoogleMap(
myLocationEnabled: true,
markers: _markers,
initialCameraPosition: initialLocation,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller); setState(() {
_markers.add(
Marker(
markerId: MarkerId(‘<MARKER_ID>’),
position: pinPosition,
icon: pinLocationIcon
)
);
});
});
}
Simple as that, and now your applications will be personalized and stand out from the rest! Here’s the Github project to the working Flutter app.
Stay tuned for other posts in Flutter regarding location-based applications. Happy Fluttering!