Exploring Google Map Compose Library For Android

An introduction to a new era for map-based Compose applications

Stephen Vinouze
Better Programming

--

Photo by Will H McMahan on Unsplash

With the recent release of the Map Compose library, it was perfect timing to try it out for the project I’m building with Jetpack Compose. In this application, I fetch a list of points of interest (POIs) from my server around a given area and mark them on the map.

I could have stuck with the View-based GoogleMap and wrapped it within a AndroidView to invoke it in the Compose world. Especially if you’d need some advanced features as the Compose library still suffers from some limitations (more on this later).

But I expect some of you are as excited as I am to discover this new library made in Compose, for Compose.

This article will walk you through how to render a map with dynamic markers. We’ll also cover how to refresh these POIs while interacting with the map (panning, zooming, rotating).

Configuring Your Map

To render a map in your mobile application, you’ll need to go through this tedious part covered in this documentation. To summarize, you must:

  • Create (or link) your Google Cloud Billing account to your project. In 2018, Google migrated to a pay-as-you-go plan that requires every project to configure upfront your payment details. In this tutorial, we’ll use a static map with the mobile SDK so we’ll stay under the radar. But depending on what you later plan for your app, there is a potential cost to consider. More information here.
  • Enable the Map SDK for your project.
  • Create an API key. You can restrict it to avoid malicious usage by linking it to the SHA-1 certificate you’re using to sign your app.
  • Add the required dependencies and link your API key to your AndroidManifest file.

The fun part begins once you’ve completed the above steps.

Rendering The Map

The library exposes a GoogleMap Composable as an entry point to render your map. Several optional arguments let you customize your map experience.

If you don’t provide any arguments, you’ll see a world map centered on the equator and the prime meridian.

The world map from lat/lng 0

If you look at the constructor details, the GoogleMap takes a CameraPositionState with a default position at the latitude and longitude 0.

We’ll want to position our map around a specific location — or your current location but I won’t cover it in this article.

The map is driven by a single source of truth: the camera’s state CameraPositionState . Whenever you interact with the map, the state gets updated and a recomposition occurs.

As an example, I’ll center the camera around Bordeaux 🍷🥖🇫🇷. We’ll remember those coordinates using the rememberCameraPositionState function and pass them to the GoogleMap Composable. Here, I’ve arbitrarily set the zoom level to 12 to overlook the whole Bordeaux area.

When we run the app, this is what we get:

Bordeaux area

That’s all you need to display a map with Map Compose! Impressive no? Now, it would be great to display some POIs.

Displaying POIs Within a Perimeter

The GoogleMap Composable accepts a content lambda as its last parameter to let you draw on the map. For that, we’ll want to look at the Marker Composable. It accepts a MarkerState — a wrapper for a LatLng object.

By default, the Marker will show a classic red pinpoint. You can customize it with its other parameters.

For my purposes, I’ve developed an API with a route that returns a list of objects around a certain location. It takes three parameters:

  • a latitude
  • a longitude
  • a radius (in meters)

From the CameraPositionState, we can retrieve the center position from the map’s camera:

val centerLocation = cameraPositionState.position.target
val latitude = centerLocation.latitude
val longitude = centerLocation.longitude

We’ll need some help computing the radius. The trick lies in converting the zoom level given by the map — a digit starting from 0 (the whole planet) to more than 21 — into a more human-friendly radius in meters. If you’d like to learn more about how Google renders the map, you’ll find this article very interesting.

Google provides a maps utility library that computes this distance for us by taking one corner of the map (top left for instance) with the center position.

val topLeftLocation = cameraPositionState.projection?.visibleRegion?.farLeft
val radius = SphericalUtil.computeDistanceBetween(topLeftLocation, centerLocation)

You’ll need to have your map ready to retrieve the above values (or the projection will be null). A good place to request them would be within the onMapLoaded lambda. That’s where I’ll ask my ViewModel to retrieve the locations of the POIs within the computed radius.

Finally, I iterate through my marker coordinates to generate all my Marker Composables.

To simulate this, you can create a set of coordinates and give it to your GoogleMap Composable the same way you’d fetch them from an API.

Here is what you would see with the coordinates my server yields:

A list of markers in the Bordeaux area

But this is only a snapshot of some POIs from what the camera sees. Ultimately, you’ll want to refresh those POIs while interacting with your map.

Interacting With The Map

The map lets you control what to display through gestures such as panning, zooming, or even rotating. Those interactions are bound to change what the camera sees. Hence, your POIs must change accordingly.

With Map Compose, you’ll once again rely on your single source of truth: the CameraPositionState. It has a property called isMoving that will turn to true as soon as the map moves. And it goes back to false when the camera becomes idle.

You may be tempted to simply wrap your fetching method within an if-condition like this:

Although that would work, you’ll create excessive requests to your server!

That’s due to the nature of how the recomposition works. For instance, if you pan the map, the camera’s center location will change. As the map moves, you’ll refresh your POIs with the newly observed centered position. The fetched POIs are likely to differ, so your state will change, leading to a recomposition.

It means you’ll hit again the condition and refresh your POIs until your state stops changing — that could lead to a tremendous amount of requests!

Instead, you should treat this interaction as a side-effect and wrap it accordingly. Since you’d like to trigger such an effect when the isMoving state changes, you can use LaunchEffect and pass this value as its key. Finally, refresh your POIs when the map stops moving.

I’ve made a short video to illustrate how the refresh behaves. Even when I fling fast the map — the map blurs until the animation stops — the POIs get displayed when the map becomes idle.

Refreshing POIs while panning and zooming the map

And what’s interesting with this Compose approach is the performance behind it! Because of the recomposition system, you can pass the list of POIs and the map will display only those markers. No need to take care of the lifecycle nor to make sure you’ve properly cleared your previous markers that the camera no longer displays. Not to mention the code is highly readable and just requires a few lines of code.

If you’re still here, congratulations! We’ve covered some basics on how to use the Map Compose and displayed POIs while interacting with the map.

Even though that might be enough for some of you, there are some things worth mentioning before a full dive into this library.

Gotchas and limitations

There are things that you may expect to have but are not yet ready with Map Compose. That’s the case with clustering.

This mechanism gathers a set of POIs within the same area. Without it, you may end up having a map crammed with markers!

Can you spot what you’re looking for amongst these 10 000 POIs?

Not only you can’t see much on the map, but you can imagine how poor the performance will be when trying to draw all these markers.

You can achieve clustering thanks to the ClusterManager from the util library mentioned above. Yet, this manager needs a GoogleMap from the View library — it can be confusing since the name matches, but it’s not the GoogleMap from the Compose library.

So as I’m writing these lines, the Map Compose library doesn’t support yet clustering. You can follow the open issue here.

In the meantime, you’re left with a couple of options:

  1. Keep using the former map API until clustering is supported.
  2. Restrict your zoom level to a reasonable value. That could prevent rendering countless POIs as much as keeping the map from lagging. But this won’t replace a good clustering since your density of POIs may vary from one area to another. Your user experience may be degraded depending on the data you want to display.

With that in mind, you’re all set to start using the Map Compose library. I’m positive it’ll become a game-changer for the map-based application as soon as it will catch up with the missing features.

In the meantime, keep track of their GitHub repository for the upcoming releases. Have fun with the Map Compose!

--

--

✍️ Content creator | 👀 200k Views | 🤖 Keen interest in Android and Jetpack Compose | 🤝 Support me: https://medium.com/@s.vinouze/membership