This repository containsJetpack Composecomponents for the Maps SDK for Android.
- Kotlin-enabled project
- Jetpack Compose-enabled project (seereleasesfor the required version of Jetpack Compose)
- AnAPI key
- API level 21+
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
)
Configuring the map can be done by passing aMapProperties
object into the
GoogleMap
composable, or for UI-related configurations, useMapUiSettings
.
MapProperties
andMapUiSettings
should be your first go-to for configuring
the map. For any other configuration not present in those two classes, use
googleMapOptionsFactory
to provide aGoogleMapOptions
instance 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")
}
)
Camera changes and updates can be observed and controlled viaCameraPositionState
.
Note:CameraPositionState
is the source of truth for anything camera
related. So, providing a camera position inGoogleMapOptions
will 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 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"
)
}
You can customize a marker's info window contents by using the
MarkerInfoWindowContent
element, or if you want to customize the entire info
window, use theMarkerInfoWindow
element instead. Both of these elements
accept acontent
parameter 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)
}
}
Certain use cases require extending theGoogleMap
object 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
GoogleMap
object which you can obtain with theMapEffect
composable.
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, thatMapEffect
is designed as an escape hatch and has certain
gotchas. TheGoogleMap
composable provided by the Maps Compose library manages
properties while theGoogleMap
is in composition, and so, setting properties
on theGoogleMap
instance provided in theMapEffect
composable may have
unintended consequences. For instance, using the utility library to perform
clustering as shown in the example above will breakonClick
events from
being propagated onMarker
composables as shown in the comment above. So, if
you are using clustering, stick with adding markers through theClusterManager
and don't useMarker
composables (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.
This library also provides optional composable widgets in themaps-compose-widgets
library that you can use alongside theGoogleMap
composable.
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. ADisappearingScaleBar
is 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 theDisappearingScaleBar
in the upper left corner and the normal baseScaleBar
in the upper right:
Both versions of this widget leverage theCameraPositionState
inmaps-compose
and 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, theDisappearingScaleBar
animations can be configured.
This repository includes asample app.
To run it, you'll have to:
- Get aMaps API key
- Add an entry in
local.properties
that looks likeMAPS_API_KEY=YOUR_KEY
- Build and run
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'
}
You can learn more about all the extensions provided by this library by reading thereference documents.
Contributions are welcome and encouraged! Seecontributingfor more info.
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.