Creating Custom Markers on Google Maps in Flutter Apps

creating-custom-markers-on-google-maps-in-flutter-apps-0

When it comes to showing maps in your Flutter application, there are two main options. You can either use flutter_map which is a Leaflet implementation for Flutter and will work with a number of free and paid map providers, or use google_maps_flutter if you want the more popular Google Maps.

For this blog post, I will be using google_maps_flutter, which is made and maintained by the Flutter team at Google.

If this is your first time using Google Maps I recommend checking out Google codelab for maps in Flutter. What I needed was to draw some custom markers like those in the image below.

Markers exmaple

Example of what we are trying to achieve

You would expect that you could pass a Widget for marker, just like when using anything else in Flutter. But it’s not that easy as Marker accepts BitmapDescriptor for the icon. I’m going to explain how you can achieve that.

The Native way

What is the BitmapDescriptor? If you’ve ever worked with iOS or Android native Google Maps SDK this will not surprise you.

BitmapDescriptor is an object that defines bitmap which can then be used by Google Maps to draw it on a map. It is used in both platforms and it’s the only way you can add custom icons for markers. So, this limitation comes from the native Google Maps SDK which google_maps_flutter uses, as there is no full flutter implementation for a complex widget like maps yet.

BitmapDescriptor will accept an asset image or a bitmap. For our case, marker text is dynamic, so static asset image is not a good fit. As for a bitmap, in native Android/iOS there’s a way to convert your View to Bitmap (or UIView to UIImage) and you can pass that to BitmapDescriptor.

The Flutter way

You can do it the same way in the Flutter, but it’s a bit trickier. Mostly because you cannot inflate/load a widget somewhere on the side and convert that to bitmap. You need to draw a widget in the screen view hierarchy and then fetch its painted bitmap.

The key thing you need to know is that each Widget is more like data class and you access bitmap in its associated RenderObject, which gets created during the build phase.

1. Getting the RenderObject

Draw your widget to the screen. This is from some widgets build method that will inflate our markers. We use GlobalKey so we have reference to this widget after it’s built.

	
final markerKey = GlobalKey();
return RepaintBoundary(
  key: markerKey,
  child: customMarkerWidget,
);

Now you can put that GlobalKey to use after the widget is built:

	
markerKey.currentContext.findRenderObject();

2. Converting to Uint8List

Uint8List is basically a list of unsigned integers that represents bitmap in this case as there is no special Bitmap class in the dart. BitmapDescriptor will also accept Uint8List so we need to do some converting to get from RenderObject to Uint8List. I have all that in the following method:

	
Future<Uint8List> getUint8List(GlobalKey markerKey) async {
  RenderRepaintBoundary boundary =
  markerKey.currentContext.findRenderObject();
  var image = await boundary.toImage(pixelRatio: 2.0);
  ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
  return byteData.buffer.asUint8List();
}

3. When is widget finished with the build?

I’ve shown you how to get RenderObject and convert it to Uint8List, but when do you call this method?

We need to get our bitmaps right after they are drawn. There are multiple ways to do this, I use tiny AfterLayoutMixin available here.

	
@override
void afterFirstLayout(BuildContext context) {
  getUint8List(markerKey).then((markerBitmap) =>
    // Set state and use your markerBitmap
  );
}

4. Create the Marker

There you have it. Create the Marker with Uint8List and plug it into the Google Map:

	
Marker(
    position: position,
    icon: BitmapDescriptor.fromBytes(markerUint8List)
)

You should end up with something like this:

Map with markers

That looks like a fun roadtrip

I have extracted all this in one simple-to-use class MarkerGenerator. It’s a bit more advanced than the guide explained here. It uses an overlay to build marker widgets and provide List<Uint8List> through the callback. The gist is available here.

Copy it to your project. Then you use MarkerGenerator this way:

	
@override  void initState() {    
    super.initState();     
    MarkerGenerator(markerWidgets, (bitmaps) {      
        setState(() {        
            markers = mapBitmapsToMarkers(bitmaps);      
        });    
    }).generate(context);  
}

Time flies, Flutter saves it

When working in Flutter, some things take more time as opposed to writing them in native Android/iOS, as it happened this time with custom map markers. But overall, I believe that Flutter is definitely a timesaver and a great choice for most apps.