In a previous article back in July of 2020 I outlined how Google Maps support could be added to a Jetpack Compose app based on what was available at that time (using AndroidView). I mentioned in that article that it “seems like this is something that ultimately will exist as specific Jetpack Compose @Composable” and was very happy to see following tweet recently about availability of new Maps Compose component. In this post I’m going to describe how the Galway Bus Kotlin Multiplatform app was updated to use this component.


Maps Compose

Firstly, as outlined in the Maps Compose github page, the following dependencies need to be added to the app’s build.gradle.kts file (based on current versions at ths time).

implementation("com.google.maps.android:maps-compose:1.0.0")
implementation("com.google.android.gms:play-services-maps:18.0.2")

As shown in the screenshot below the app shows the bus stops near to a particular location, both as markers on the map and also in a list as shown below.

galway bus app screenshot

We need to be able to drive the map’s camera position from location, a StateFlow in the view model (the updating of which also in turn triggers querying for list of bus stops close to that location and updating associated state). This can be set in a number of ways:

  • from the device’s location on startup
  • when the user presses the “home” button in the app bar (centering in particular location in the city)
  • as the user pans/changes location on the map

We can manage the setting and observing of changes to that camera position using the component’s CameraPositionState.

val currentLocation by viewModel.location.collectAsState()

val cameraPositionState = rememberCameraPositionState {
    position = CameraPosition.fromLatLngZoom(LatLng(currentLocation.latitude, currentLocation.longitude), 15f)
}

Now we need to manage the interdependent relationship between the location state in the view model and the map’s camera position. Changing one updates the other and vice versa. The way we can do this is using Compose’s snapshotFlow as shown below. Note that I had initially modelled that first dependency using derivedStateOf but ran in to some snapshot related state issues when used with the subsequent snapshotFlow (hope to get chance to dig a bit deeper in to that and figure out why).

snapshotFlow { currentLocation }
    .collect {
        cameraPositionState.position = CameraPosition.fromLatLngZoom(LatLng(currentLocation.latitude, currentLocation.longitude), 15f)
    }

snapshotFlow { cameraPositionState.position }
    .collect {
        viewModel.setLocation(Location(it.target.latitude, it.target.longitude))
    }

Lastly this is how we setup GoogleMap, passing in the cameraPositionState from above and also other initialisation properties along with adding Markers for each of the bus stops.

val mapProperties by remember { mutableStateOf(MapProperties(isMyLocationEnabled = true)) }
val uiSettings by remember { mutableStateOf(MapUiSettings(myLocationButtonEnabled = true)) }

GoogleMap(
    modifier = modifier,
    cameraPositionState = cameraPositionState,
    properties = mapProperties,
    uiSettings = uiSettings
) {
    stops.forEach { stop ->
        val busStopLocation = LatLng(stop.latitude, stop.longitude)
        val icon = bitmapDescriptorFromVector(context, R.drawable.ic_stop, R.color.mapMarkerGreen)
        Marker(position = busStopLocation, title = stop.shortName, icon = icon)
    }
}

A different map in this application shows the live position of buses for a particular route. This uses GoogleMap as above but also needs to set bounds of map based on the positions of those buses. This can be done using something like the following.

val builder = LatLngBounds.Builder()
busInfoList.forEach { bus ->
    val busLocation = LatLng(bus.latitude, bus.longitude)
    builder.include(busLocation)
}

cameraPositionState.move(CameraUpdateFactory.newLatLngBounds(builder.build(), 64))

galway bus app screenshot

The above changes have been pushed to the GalwayBus repo and new version published to Play Store. Note also that Maps Compose’s github repo also includes a sample app that demonstrates other capabilties provided by the library.


Featured in Android Weekly Issue #504, Kotlin Weekly Issue #288 and jetc.dev Newsletter Issue #103