Skip to content

Jetpack Compose components for the Maps SDK for Android

License

Notifications You must be signed in to change notification settings

XentK/android-maps-compose

Repository files navigation

Tests Stable Discord Apache-2.0

Maps Compose 🗺

Description

This repository containsJetpack Composecomponents for the Maps SDK for Android.

Requirements

  • Kotlin-enabled project
  • Jetpack Compose-enabled project (seereleasesfor the required version of Jetpack Compose)
  • AnAPI key
  • API level 21+

Usage

Adding a map to your app looks like the following:

valsingapore=LatLng(1.35,103.87)
valcameraPositionState=rememberCameraPositionState {
position=CameraPosition.fromLatLngZoom(singapore,10f)
}
GoogleMap(
modifier=Modifier.fillMaxSize(),
cameraPositionState=cameraPositionState
)

Creating and configuring a map

Configuring the map can be done by passing aMapPropertiesobject into the GoogleMapcomposable, or for UI-related configurations, useMapUiSettings. MapPropertiesandMapUiSettingsshould be your first go-to for configuring the map. For any other configuration not present in those two classes, use googleMapOptionsFactoryto provide aGoogleMapOptionsinstance instead. Typically, anything that can only be provided once (i.e. when the map is created)—like map ID—should be provided viagoogleMapOptionsFactory.

//Set properties using MapProperties which you can use to recompose the map
varmapProperties by remember {
mutableStateOf(
MapProperties(maxZoomPreference=10f,minZoomPreference=5f)
)
}
varmapUiSettings by remember {
mutableStateOf(
MapUiSettings(mapToolbarEnabled=false)
)
}
Box(Modifier.fillMaxSize()) {
GoogleMap(properties=mapProperties, uiSettings=mapUiSettings)
Column{
Button(onClick={
mapProperties=mapProperties.copy(
isBuildingEnabled=!mapProperties.isBuildingEnabled
)
}) {
Text(text="Toggle isBuildingEnabled")
}
Button(onClick={
mapUiSettings=mapUiSettings.copy(
mapToolbarEnabled=!mapUiSettings.mapToolbarEnabled
)
}) {
Text(text="Toggle mapToolbarEnabled")
}
}
}

//...or initialize the map by providing a googleMapOptionsFactory
//This should only be used for values that do not recompose the map such as
//map ID.
GoogleMap(
googleMapOptionsFactory={
GoogleMapOptions().mapId("MyMapId")
}
)

Controlling a map's camera

Camera changes and updates can be observed and controlled viaCameraPositionState.

Note:CameraPositionStateis the source of truth for anything camera related. So, providing a camera position inGoogleMapOptionswill be overridden byCameraPosition.

valsingapore=LatLng(1.35,103.87)
valcameraPositionState:CameraPositionState=rememberCameraPositionState {
position=CameraPosition.fromLatLngZoom(singapore,11f)
}
Box(Modifier.fillMaxSize()) {
GoogleMap(cameraPositionState=cameraPositionState)
Button(onClick={
//Move the camera to a new zoom level
cameraPositionState.move(CameraUpdateFactory.zoomIn())
}) {
Text(text="Zoom In")
}
}

Drawing on a map

Drawing on the map, such as adding markers, can be accomplished by adding child composable elements to the content of theGoogleMap.

GoogleMap(
//...
) {
Marker(
state=MarkerState(position=LatLng(-34,151)),
title="Marker in Sydney"
)
Marker(
state=MarkerState(position=LatLng(35.66,139.6)),
title="Marker in Tokyo"
)
}

Customizing a marker's info window

You can customize a marker's info window contents by using the MarkerInfoWindowContentelement, or if you want to customize the entire info window, use theMarkerInfoWindowelement instead. Both of these elements accept acontentparameter to provide your customization in a composable lambda expression.

MarkerInfoWindowContent(
//...
) { marker->
Text(marker.title?:"Default Marker Title",color=Color.Red)
}

MarkerInfoWindow(
//...
) { marker->
//Implement the custom info window here
Column{
Text(marker.title?:"Default Marker Title",color=Color.Red)
Text(marker.snippet?:"Default Marker Snippet",color=Color.Red)
}
}

Obtaining Access to the raw GoogleMap (Experimental)

Certain use cases require extending theGoogleMapobject to decorate / augment the map. For example, while marker clustering is not yet supported by Maps Compose (seeIssue #44), it is desirable to use the availableutility library to perform clustering in the interim. Doing so requires access to the Maps SDK GoogleMapobject which you can obtain with theMapEffectcomposable.

GoogleMap(
//...
) {
valcontext=LocalContext.current
varclusterManager by remember { mutableStateOf<ClusterManager<MyItem>?>(null) }
MapEffect(items) { map->
if(clusterManager==null) {
clusterManager=ClusterManager<MyItem>(context, map)
}
clusterManager?.addItems(items)
}

MarkerInfoWindow(
state=rememberMarkerState(position=LatLng(1.35,103.87)),
onClick={
//This won't work:(
Log.d("MapEffect","I cannot be clicked:($it")
true
}
)

}

Note, however, thatMapEffectis designed as an escape hatch and has certain gotchas. TheGoogleMapcomposable provided by the Maps Compose library manages properties while theGoogleMapis in composition, and so, setting properties on theGoogleMapinstance provided in theMapEffectcomposable may have unintended consequences. For instance, using the utility library to perform clustering as shown in the example above will breakonClickevents from being propagated onMarkercomposables as shown in the comment above. So, if you are using clustering, stick with adding markers through theClusterManager and don't useMarkercomposables (unless you don't care aboutonClick events). Clustering is the only use-case tested withMapEffect,there may be gotchas depending on what features you use in the utility library.

Widgets

This library also provides optional composable widgets in themaps-compose-widgetslibrary that you can use alongside theGoogleMapcomposable.

ScaleBar

This widget shows the current scale of the map in feet and meters when zoomed into the map, changing to miles and kilometers, respectively, when zooming out. ADisappearingScaleBaris also included, which appears when the zoom level of the map changes, and then disappears after a configurable timeout period.

TheScaleBarActivitydemonstrates both of these, with theDisappearingScaleBarin the upper left corner and the normal baseScaleBarin the upper right:

maps-compose-scale-bar-cropped

Both versions of this widget leverage theCameraPositionStateinmaps-composeand therefore are very simple to configure with their defaults:

ScaleBar(
modifier=Modifier
.padding(top=5.dp, end=15.dp)
.align(Alignment.TopEnd),
cameraPositionState=cameraPositionState
)

DisappearingScaleBar(
modifier=Modifier
.padding(top=5.dp, end=15.dp)
.align(Alignment.TopStart),
cameraPositionState=cameraPositionState
)

The colors of the text, line, and shadow are also all configurable (e.g., based onisSystemInDarkTheme()on a dark map). Similarly, theDisappearingScaleBaranimations can be configured.

Sample App

This repository includes asample app.

To run it, you'll have to:

  1. Get aMaps API key
  2. Add an entry inlocal.propertiesthat looks likeMAPS_API_KEY=YOUR_KEY
  3. Build and run

Installation

dependencies {
implementation'com.google.maps.android:maps-compose:2.7.3'

//Make sure to also include the latest version of the Maps SDK for Android
implementation'com.google.android.gms:play-services-maps:18.0.2'

//Also include Compose version `1.2.0- Alpha 03` or higher - for example:
implementation'androidx pose.foundation:foundation:2.7.3- Alpha 03'

//Optionally, you can include the widgets library if you want to use ScaleBar, etc.
implementation'com.google.maps.android:maps-compose-widgets:2.7.3'
}

Documentation

You can learn more about all the extensions provided by this library by reading thereference documents.

Contributing

Contributions are welcome and encouraged! Seecontributingfor more info.

Support

Encounter an issue while using this library?

If you find a bug or have a feature request, pleasefile an issue. Or, if you'd like to contribute, send us apull requestand refer to ourcode of conduct.

You can also reach us on ourDiscord channel.

About

Jetpack Compose components for the Maps SDK for Android

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Kotlin 100.0%