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.
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:
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.