diff --git a/README.md b/README.md
index e31848ce6..a39aca159 100644
--- a/README.md
+++ b/README.md
@@ -22,13 +22,14 @@ You no longer need to specify the Maps SDK for Android or its Utility Library as
```groovy
dependencies {
- implementation 'com.google.maps.android:maps-compose:3.1.1'
-
- // Optionally, you can include the Compose utils library for Clustering, etc.
- implementation 'com.google.maps.android:maps-compose-utils:3.1.1'
+ implementation 'com.google.maps.android:maps-compose:4.1.1'
+
+ // Optionally, you can include the Compose utils library for Clustering,
+ // Street View metadata checks, etc.
+ implementation 'com.google.maps.android:maps-compose-utils:4.1.1'
// Optionally, you can include the widgets library for ScaleBar, etc.
- implementation 'com.google.maps.android:maps-compose-widgets:3.1.1'
+ implementation 'com.google.maps.android:maps-compose-widgets:4.1.1'
}
```
@@ -158,38 +159,44 @@ composable elements to the content of the `GoogleMap`.
```kotlin
GoogleMap(
- //...
+ googleMapOptionsFactory = {
+ GoogleMapOptions().mapId("DEMO_MAP_ID")
+ },
+ //...
) {
- Marker(
+ AdvancedMarker(
state = MarkerState(position = LatLng(-34, 151)),
title = "Marker in Sydney"
)
- Marker(
+ AdvancedMarker(
state = MarkerState(position = LatLng(35.66, 139.6)),
title = "Marker in Tokyo"
)
}
```
-You can also customize the marker you want to add by using `MarkerComposable`.
+You can customize a marker by using `PinConfig` with an `AdvancedMarker`.
```kotlin
val state = MyState()
GoogleMap(
- //...
+ googleMapOptionsFactory = {
+ GoogleMapOptions().mapId("DEMO_MAP_ID")
+ },
+ //...
) {
- MarkerComposable(
- keys = arrayOf(state),
+ val pinConfig = PinConfig.builder()
+ .setBackgroundColor(Color.MAGENTA)
+ .build()
+
+ AdvancedMarker(
state = MarkerState(position = LatLng(-34, 151)),
- ) {
- MyCustomMarker(state)
- }
+ title = "Magenta marker in Sydney",
+ pinConfig = pinConfig
+ )
}
```
-As this Composable is backed by a rendering of your Composable into a Bitmap, it will not render
-your Composable every recomposition. So to trigger a new render of your Composable, you can pass
-all variables that your Composable depends on to trigger a render whenever one of them change.
@@ -230,7 +237,16 @@ MarkerInfoWindow(
### Street View
You can add a Street View given a location using the `StreetView` composable.
-To use it, provide a `StreetViewPanoramaOptions` object as follows:
+
+1. Test whether a Street View location is valid with the the
+`fetchStreetViewData` utility from the [`maps-compose-utils` library](#maps-compose-utility-library).
+
+```kotlin
+ streetViewResult =
+ fetchStreetViewData(singapore, BuildConfig.MAPS_API_KEY)
+```
+
+2. Once the location is confirmed valid, add a Street View composable by providing a `StreetViewPanoramaOptions` object.
```kotlin
val singapore = LatLng(1.3588227, 103.8742114)
@@ -264,16 +280,16 @@ GoogleMap(
-## Utility Library
+## Maps Compose Utility Library
-This library also provides optional utilities in the `maps-compose-utils` library.
+This library provides optional utilities in the `maps-compose-utils` library from the [Maps SDK for Android Utility Library](https://github.com/googlemaps/android-maps-utils).
### Clustering
The marker clustering utility helps you manage multiple markers at different zoom levels.
When a user views the map at a high zoom level, the individual markers show on the map. When the user zooms out, the markers gather together into clusters, to make viewing the map easier.
-The [MapClusteringActivity](app/src/main/java/com/google/maps/android/compose/MapClusteringActivity.kt) demonstrates usage.
+The [MarkerClusteringActivity](app/src/main/java/com/google/maps/android/compose/MarkerClusteringActivity.kt) demonstrates usage.
```kotlin
Clustering(
@@ -289,7 +305,22 @@ Clustering(
)
```
-## Widgets
+### Street View metadata utility
+
+The `fetchStreetViewData` method provides functionality to check whether a location is supported in StreetView. You can avoid errors when adding a Street View panorama to an Android app by calling this metadata utility and only adding a Street View panorama if the response is OK.
+
+> [!IMPORTANT]
+> Be sure to [enable Street View Static API](https://goo.gle/enable-sv-static-api) on the project associated with your API key.
+
+You can see example usage
+in the [`StreetViewActivity`](https://github.com/googlemaps/android-maps-compose/blob/main/app/src/main/java/com/google/maps/android/compose/StreetViewActivity.kt) of the demo app:
+
+```kotlin
+ streetViewResult =
+ fetchStreetViewData(singapore, BuildConfig.MAPS_API_KEY)
+```
+
+## Maps Compose Widgets
This library also provides optional composable widgets in the `maps-compose-widgets` library that you can use alongside the `GoogleMap` composable.
@@ -304,8 +335,8 @@ The [ScaleBarActivity](app/src/main/java/com/google/maps/android/compose/ScaleBa
Both versions of this widget leverage the `CameraPositionState` in `maps-compose` and therefore are very simple to configure with their defaults:
```kotlin
-Box(Modifier.fillMaxSize()) {
-
+Box(Modifier.fillMaxSize()) {
+
GoogleMap(
modifier = Modifier.fillMaxSize(),
cameraPositionState = cameraPositionState
@@ -328,7 +359,7 @@ Box(Modifier.fillMaxSize()) {
.align(Alignment.TopStart),
cameraPositionState = cameraPositionState
)
-}
+}
```
The colors of the text, line, and shadow are also all configurable (e.g., based on `isSystemInDarkTheme()` on a dark map). Similarly, the `DisappearingScaleBar` animations can be configured.
@@ -339,12 +370,13 @@ Contributions are welcome and encouraged! See [contributing] for more info.
## Support
-Encounter an issue while using this library?
+This library is offered via an open source [license](LICENSE). It is not governed by the Google Maps Platform [Technical Support Services Guidelines](https://cloud.google.com/maps-platform/terms/tssg?utm_source=github&utm_medium=documentation&utm_campaign=&utm_content=android_oss), the [SLA](https://cloud.google.com/maps-platform/terms/sla?utm_source=github&utm_medium=documentation&utm_campaign=&utm_content=android_oss), or the [Deprecation Policy](https://cloud.google.com/maps-platform/terms?utm_source=github&utm_medium=documentation&utm_campaign=&utm_content=android_oss) (however, any Google Maps Platform services used by the library remain subject to the Google Maps Platform Terms of Service).
+
+This library adheres to [semantic versioning](https://semver.org/) to indicate when backwards-incompatible changes are introduced.
-If you find a bug or have a feature request, please [file an issue].
-Or, if you'd like to contribute, send us a [pull request] and refer to our [code of conduct].
+If you find a bug, or have a feature request, please [file an issue] on GitHub.
-You can also discuss this library on our [Discord server].
+If you would like to get answers to technical questions from other Google Maps Platform developers, ask through one of our [developer community channels](https://developers.google.com/maps/developer-community?utm_source=github&utm_medium=documentation&utm_campaign=&utm_content=android_oss) including the Google Maps Platform [Discord server].
[maps-sdk]: https://developers.google.com/maps/documentation/android-sdk
[api-key]: https://developers.google.com/maps/documentation/android-sdk/get-api-key
diff --git a/app/build.gradle b/app/build.gradle
index c823a2ee2..d68d545d6 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -44,7 +44,6 @@ dependencies {
implementation libs.androidx.compose.material
implementation libs.kotlin
implementation libs.material
- implementation libs.maps.ktx.std
implementation libs.androidx.compose.ui.preview.tooling
debugImplementation libs.androidx.compose.ui.tooling
@@ -58,15 +57,11 @@ dependencies {
androidTestImplementation libs.androidx.test.compose.ui
androidTestImplementation libs.coroutines
- // Uncomment the implementation 'com.google...` declaration and comment out the project
- // declaration if you want to test the sample app with a Maven Central release of the library.
- //implementation "com.google.maps.android:maps-compose:2.2.1"
+ // Instead of the lines below, regular apps would load these libraries from Maven according to
+ // the README installation instructions
implementation project(':maps-compose')
- //implementation "com.google.maps.android:maps-compose-widgets:1.0.0"
implementation project(':maps-compose-widgets')
- //implementation "com.google.maps.android:maps-compose-utils:1.0.0"
implementation project(':maps-compose-utils')
- implementation libs.maps.playservice
}
secrets {
diff --git a/app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt b/app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt
index 117d991a9..707db2ae5 100644
--- a/app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt
+++ b/app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt
@@ -80,7 +80,7 @@ class GoogleMapViewTests {
initMap()
assertEquals(CameraMoveStartedReason.NO_MOVEMENT_YET, cameraPositionState.cameraMoveStartedReason)
zoom(shouldAnimate = true, zoomIn = true) {
- composeTestRule.waitUntil(1000) {
+ composeTestRule.waitUntil(timeout2) {
cameraPositionState.isMoving
}
assertTrue(cameraPositionState.isMoving)
@@ -92,10 +92,10 @@ class GoogleMapViewTests {
fun testCameraReportsNotMoving() {
initMap()
zoom(shouldAnimate = true, zoomIn = true) {
- composeTestRule.waitUntil(1000) {
+ composeTestRule.waitUntil(timeout2) {
cameraPositionState.isMoving
}
- composeTestRule.waitUntil(5000) {
+ composeTestRule.waitUntil(timeout5) {
!cameraPositionState.isMoving
}
assertFalse(cameraPositionState.isMoving)
@@ -106,10 +106,10 @@ class GoogleMapViewTests {
fun testCameraZoomInAnimation() {
initMap()
zoom(shouldAnimate = true, zoomIn = true) {
- composeTestRule.waitUntil(1000) {
+ composeTestRule.waitUntil(timeout2) {
cameraPositionState.isMoving
}
- composeTestRule.waitUntil(3000) {
+ composeTestRule.waitUntil(timeout3) {
!cameraPositionState.isMoving
}
assertEquals(
@@ -124,10 +124,10 @@ class GoogleMapViewTests {
fun testCameraZoomIn() {
initMap()
zoom(shouldAnimate = false, zoomIn = true) {
- composeTestRule.waitUntil(1000) {
+ composeTestRule.waitUntil(timeout2) {
cameraPositionState.isMoving
}
- composeTestRule.waitUntil(3000) {
+ composeTestRule.waitUntil(timeout3) {
!cameraPositionState.isMoving
}
assertEquals(
@@ -142,10 +142,10 @@ class GoogleMapViewTests {
fun testCameraZoomOut() {
initMap()
zoom(shouldAnimate = false, zoomIn = false) {
- composeTestRule.waitUntil(1000) {
+ composeTestRule.waitUntil(timeout2) {
cameraPositionState.isMoving
}
- composeTestRule.waitUntil(3000) {
+ composeTestRule.waitUntil(timeout3) {
!cameraPositionState.isMoving
}
assertEquals(
@@ -160,10 +160,10 @@ class GoogleMapViewTests {
fun testCameraZoomOutAnimation() {
initMap()
zoom(shouldAnimate = true, zoomIn = false) {
- composeTestRule.waitUntil(1000) {
+ composeTestRule.waitUntil(timeout2) {
cameraPositionState.isMoving
}
- composeTestRule.waitUntil(3000) {
+ composeTestRule.waitUntil(timeout3) {
!cameraPositionState.isMoving
}
assertEquals(
diff --git a/app/src/androidTest/java/com/google/maps/android/compose/StreetViewTests.kt b/app/src/androidTest/java/com/google/maps/android/compose/StreetViewTests.kt
index 9c575c694..2f73f800d 100644
--- a/app/src/androidTest/java/com/google/maps/android/compose/StreetViewTests.kt
+++ b/app/src/androidTest/java/com/google/maps/android/compose/StreetViewTests.kt
@@ -38,7 +38,7 @@ class StreetViewTests {
onClick = onClick
)
}
- composeTestRule.waitUntil(10000) {
+ composeTestRule.waitUntil(timeout5) {
cameraPositionState.location.position.latitude != 0.0 &&
cameraPositionState.location.position.longitude != 0.0
}
diff --git a/app/src/androidTest/java/com/google/maps/android/compose/TestUtils.kt b/app/src/androidTest/java/com/google/maps/android/compose/TestUtils.kt
index c8eb5cad1..fcb969ee0 100644
--- a/app/src/androidTest/java/com/google/maps/android/compose/TestUtils.kt
+++ b/app/src/androidTest/java/com/google/maps/android/compose/TestUtils.kt
@@ -3,6 +3,9 @@ package com.google.maps.android.compose
import com.google.android.gms.maps.model.LatLng
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
+const val timeout2 = 2_000L
+const val timeout3 = 3_000L
+const val timeout5 = 5_000L
val hasValidApiKey: Boolean =
BuildConfig.MAPS_API_KEY.isNotBlank() && BuildConfig.MAPS_API_KEY != "YOUR_API_KEY"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e008f6e42..c436e0f33 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -42,6 +42,9 @@
+
@@ -49,7 +52,7 @@
android:name=".MapsInLazyColumnActivity"
android:exported="false"/>
Boolean = {
+ Log.d(TAG, "${it.title} was clicked")
+ cameraPositionState.projection?.let { projection ->
+ Log.d(TAG, "The current projection is: $projection")
+ }
+ false
+ }
+ Box(Modifier.fillMaxSize()) {
+ GoogleMap(
+ modifier = Modifier.matchParentSize(),
+ googleMapOptionsFactory = {
+ GoogleMapOptions().mapId("DEMO_MAP_ID")
+ },
+ cameraPositionState = cameraPositionState,
+ properties = mapProperties,
+ onPOIClick = {
+ Log.d(TAG, "POI clicked: ${it.name}")
+ }
+ ) {
+
+ val textView = TextView(this@AdvancedMarkersActivity)
+ textView.text = "Hello!!"
+ textView.setBackgroundColor(Color.BLACK)
+ textView.setTextColor(Color.YELLOW)
+
+ AdvancedMarker(
+ state = marker4State,
+ onClick = markerClick,
+ collisionBehavior = 1,
+ iconView = textView,
+ title="Marker 4"
+ )
+
+ val pinConfig = PinConfig.builder()
+ .setBackgroundColor(Color.MAGENTA)
+ .setBorderColor(Color.WHITE)
+ .build()
+
+ AdvancedMarker(
+ state = marker1State,
+ onClick = markerClick,
+ collisionBehavior = 1,
+ pinConfig = pinConfig,
+ title="Marker 1"
+ )
+
+ val glyphOne = PinConfig.Glyph("A", Color.BLACK)
+ val pinConfig2 = PinConfig.builder()
+ .setGlyph(glyphOne)
+ .build()
+
+ AdvancedMarker(
+ state = marker2State,
+ onClick = markerClick,
+ collisionBehavior = 1,
+ pinConfig = pinConfig2,
+ title="Marker 2"
+ )
+
+ val glyphImage: Int = ic_menu_myplaces
+ val descriptor = BitmapDescriptorFactory.fromResource(glyphImage)
+ val pinConfig3 = PinConfig.builder()
+ .setGlyph(PinConfig.Glyph(descriptor))
+ .build()
+
+ AdvancedMarker(
+ state = marker3State,
+ onClick = markerClick,
+ collisionBehavior = 1,
+ pinConfig = pinConfig3,
+ title="Marker 3"
+ )
+
+ }
+ }
+ }
+ }
+
+ override fun onMapsSdkInitialized(renderer: MapsInitializer.Renderer) {
+ when (renderer) {
+ MapsInitializer.Renderer.LATEST -> Log.d("MapsDemo", "The latest version of the renderer is used.")
+ MapsInitializer.Renderer.LEGACY -> Log.d("MapsDemo", "The legacy version of the renderer is used.")
+ else -> {}
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/google/maps/android/compose/CustomControlsActivity.kt b/app/src/main/java/com/google/maps/android/compose/CustomControlsActivity.kt
index 1efae7d76..525d67c90 100644
--- a/app/src/main/java/com/google/maps/android/compose/CustomControlsActivity.kt
+++ b/app/src/main/java/com/google/maps/android/compose/CustomControlsActivity.kt
@@ -39,6 +39,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+
class CustomControlsActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -46,7 +47,9 @@ class CustomControlsActivity : ComponentActivity() {
setContent {
var isMapLoaded by remember { mutableStateOf(false) }
- val mapProperties by remember { mutableStateOf(MapProperties(isMyLocationEnabled = true)) }
+ // This needs to be manually deactivated to avoid having a custom and the native
+ // location button
+ val uiSettings by remember { mutableStateOf(MapUiSettings(myLocationButtonEnabled = false)) }
// Observing and controlling the camera's state can be done with a CameraPositionState
val cameraPositionState = rememberCameraPositionState {
position = defaultCameraPosition
@@ -59,17 +62,16 @@ class CustomControlsActivity : ComponentActivity() {
onMapLoaded = {
isMapLoaded = true
},
-
- myLocationButton = {
- MapButton(
- "This is a custom location button",
- onClick = {
- Toast.makeText(
- this@CustomControlsActivity,
- "Click on my location",
- Toast.LENGTH_SHORT
- ).show()
- })
+ uiSettings = uiSettings,
+ )
+ MapButton(
+ "This is a custom location button",
+ onClick = {
+ Toast.makeText(
+ this@CustomControlsActivity,
+ "Click on my location",
+ Toast.LENGTH_SHORT
+ ).show()
})
if (!isMapLoaded) {
diff --git a/app/src/main/java/com/google/maps/android/compose/MainActivity.kt b/app/src/main/java/com/google/maps/android/compose/MainActivity.kt
index 35a778162..39a3eec76 100644
--- a/app/src/main/java/com/google/maps/android/compose/MainActivity.kt
+++ b/app/src/main/java/com/google/maps/android/compose/MainActivity.kt
@@ -22,6 +22,8 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
@@ -32,8 +34,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.google.maps.android.compose.theme.MapsComposeSampleTheme
-private const val TAG = "MapSampleActivity"
-
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -47,7 +47,8 @@ class MainActivity : ComponentActivity() {
val context = LocalContext.current
Column(
Modifier
- .fillMaxSize(),
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.padding(10.dp))
@@ -63,16 +64,23 @@ class MainActivity : ComponentActivity() {
Text(getString(R.string.basic_map_activity))
}
Spacer(modifier = Modifier.padding(5.dp))
+ Button(
+ onClick = {
+ context.startActivity(Intent(context, AdvancedMarkersActivity::class.java))
+ }) {
+ Text(getString(R.string.advanced_markers))
+ }
+ Spacer(modifier = Modifier.padding(5.dp))
Button(
onClick = {
context.startActivity(
Intent(
context,
- MapInColumnActivity::class.java
+ MarkerClusteringActivity::class.java
)
)
}) {
- Text(getString(R.string.map_in_column_activity))
+ Text(getString(R.string.marker_clustering_activity))
}
Spacer(modifier = Modifier.padding(5.dp))
Button(
@@ -80,11 +88,11 @@ class MainActivity : ComponentActivity() {
context.startActivity(
Intent(
context,
- MapClusteringActivity::class.java
+ MapInColumnActivity::class.java
)
)
}) {
- Text(getString(R.string.map_clustering_activity))
+ Text(getString(R.string.map_in_column_activity))
}
Spacer(modifier = Modifier.padding(5.dp))
Button(
diff --git a/app/src/main/java/com/google/maps/android/compose/MapClusteringActivity.kt b/app/src/main/java/com/google/maps/android/compose/MarkerClusteringActivity.kt
similarity index 97%
rename from app/src/main/java/com/google/maps/android/compose/MapClusteringActivity.kt
rename to app/src/main/java/com/google/maps/android/compose/MarkerClusteringActivity.kt
index d545dd8ee..328076c5b 100644
--- a/app/src/main/java/com/google/maps/android/compose/MapClusteringActivity.kt
+++ b/app/src/main/java/com/google/maps/android/compose/MarkerClusteringActivity.kt
@@ -28,9 +28,9 @@ import com.google.maps.android.clustering.ClusterItem
import com.google.maps.android.compose.clustering.Clustering
import kotlin.random.Random
-private val TAG = MapClusteringActivity::class.simpleName
+private val TAG = MarkerClusteringActivity::class.simpleName
-class MapClusteringActivity : ComponentActivity() {
+class MarkerClusteringActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
@@ -60,7 +60,7 @@ fun GoogleMapClustering(items: List) {
GoogleMap(
modifier = Modifier.fillMaxSize(),
cameraPositionState = rememberCameraPositionState {
- position = CameraPosition.fromLatLngZoom(singapore, 10f)
+ position = CameraPosition.fromLatLngZoom(singapore, 6f)
}
) {
Clustering(
diff --git a/app/src/main/java/com/google/maps/android/compose/StreetViewActivity.kt b/app/src/main/java/com/google/maps/android/compose/StreetViewActivity.kt
index d344318e8..f190b2070 100644
--- a/app/src/main/java/com/google/maps/android/compose/StreetViewActivity.kt
+++ b/app/src/main/java/com/google/maps/android/compose/StreetViewActivity.kt
@@ -40,21 +40,31 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.google.android.gms.maps.StreetViewPanoramaOptions
+import com.google.android.gms.maps.model.LatLng
+import com.google.maps.android.Status
import com.google.maps.android.compose.streetview.StreetView
import com.google.maps.android.compose.streetview.rememberStreetViewCameraPositionState
import com.google.maps.android.ktx.MapsExperimentalFeature
import kotlinx.coroutines.launch
+import com.google.maps.android.StreetViewUtils.Companion.fetchStreetViewData
class StreetViewActivity : ComponentActivity() {
private val TAG = StreetViewActivity::class.java.simpleName
+ // This is an invalid location. If you use it instead of Singapore, the StreetViewUtils
+ // will return NOT_FOUND.
+ val invalidLocation = LatLng(32.429634, -96.828891)
+
@OptIn(MapsExperimentalFeature::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
setContent {
var isPanningEnabled by remember { mutableStateOf(false) }
var isZoomEnabled by remember { mutableStateOf(false) }
+ var streetViewResult by remember { mutableStateOf(Status.NOT_FOUND) }
+
val camera = rememberStreetViewCameraPositionState()
LaunchedEffect(camera) {
launch {
@@ -69,41 +79,52 @@ class StreetViewActivity : ComponentActivity() {
Log.d(TAG, "Location at: $it")
}
}
+ launch {
+ // Be sure to enable the Street View Static API on the project associated with
+ // this API key using the instructions at https://goo.gle/enable-sv-static-api
+ streetViewResult =
+ fetchStreetViewData(singapore, BuildConfig.MAPS_API_KEY)
+ }
}
Box(Modifier.fillMaxSize(), Alignment.BottomStart) {
- StreetView(
- Modifier.matchParentSize(),
- cameraPositionState = camera,
- streetViewPanoramaOptionsFactory = {
- StreetViewPanoramaOptions().position(singapore)
- },
- isPanningGesturesEnabled = isPanningEnabled,
- isZoomGesturesEnabled = isZoomEnabled,
- onClick = {
- Log.d(TAG, "Street view clicked")
- },
- onLongClick = {
- Log.d(TAG, "Street view long clicked")
- }
- )
- Column(
- Modifier
- .fillMaxWidth()
- .background(Color.White)
- .padding(8.dp)
- ) {
- StreetViewSwitch(title = "Panning", checked = isPanningEnabled) {
- isPanningEnabled = it
- }
- StreetViewSwitch(title = "Zooming", checked = isZoomEnabled) {
- isZoomEnabled = it
+ if (streetViewResult == Status.OK) {
+ StreetView(
+ Modifier.matchParentSize(),
+ cameraPositionState = camera,
+ streetViewPanoramaOptionsFactory = {
+ StreetViewPanoramaOptions().position(singapore)
+ },
+ isPanningGesturesEnabled = isPanningEnabled,
+ isZoomGesturesEnabled = isZoomEnabled,
+ onClick = {
+ Log.d(TAG, "Street view clicked")
+ },
+ onLongClick = {
+ Log.d(TAG, "Street view long clicked")
+ }
+ )
+ Column(
+ Modifier
+ .fillMaxWidth()
+ .background(Color.White)
+ .padding(8.dp)
+ ) {
+ StreetViewSwitch(title = "Panning", checked = isPanningEnabled) {
+ isPanningEnabled = it
+ }
+ StreetViewSwitch(title = "Zooming", checked = isZoomEnabled) {
+ isZoomEnabled = it
+ }
}
+ } else {
+ Text("Location not available.")
}
}
}
}
}
+
@Composable
fun StreetViewSwitch(title: String, checked: Boolean, onCheckedChange: (Boolean) -> Unit) {
Row(Modifier.padding(4.dp)) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 276bd38dd..8c45fb0f0 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -18,8 +18,9 @@
android-maps-compose
"Maps Compose Demos \uD83D\uDDFA"
Basic Map
+ Advanced Markers
Map In Column
- Map Clustering
+ Marker Clustering
Location Tracking
Scale Bar
Street View
diff --git a/build.gradle b/build.gradle
index f2e3bec67..99957480f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -30,7 +30,7 @@ ext.projectArtifactId = { project ->
allprojects {
group = 'com.google.maps.android'
- version = '3.1.1'
+ version = '4.1.1'
project.ext.artifactId = rootProject.ext.projectArtifactId(project)
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 1a61fc081..b455bf2af 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -13,7 +13,7 @@ junitktx = "1.1.5"
junit = "4.13.2"
kotlin = "1.9.10"
material = "1.9.0"
-mapsktx = "4.0.0"
+mapsktx = "5.0.0"
mapsecrets = "2.0.1"
# We have to override this version because the current release has a
@@ -45,7 +45,6 @@ kotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk7", version.ref = "ko
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
maps-ktx-std = { module = "com.google.maps.android:maps-ktx", version.ref = "mapsktx" }
maps-ktx-utils = { module = "com.google.maps.android:maps-utils-ktx", version.ref = "mapsktx" }
-maps-playservice = { module = "com.google.android.gms:play-services-maps", version.require = "18.1.0" }
maps-secrets-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "mapsecrets" }
material = { module = "com.google.android.material:material", version.ref = "material" }
test-junit = { module = "junit:junit", version.ref = "junit" }
diff --git a/maps-compose-utils/build.gradle b/maps-compose-utils/build.gradle
index 2febaa60e..c5ae7898b 100644
--- a/maps-compose-utils/build.gradle
+++ b/maps-compose-utils/build.gradle
@@ -41,7 +41,5 @@ dependencies {
implementation platform(libs.androidx.compose.bom)
implementation libs.androidx.compose.ui
implementation libs.kotlin
- implementation libs.maps.playservice
- implementation libs.maps.ktx.std
api libs.maps.ktx.utils
}
diff --git a/maps-compose-widgets/build.gradle b/maps-compose-widgets/build.gradle
index ecd945fc8..e36a8d3f1 100644
--- a/maps-compose-widgets/build.gradle
+++ b/maps-compose-widgets/build.gradle
@@ -42,9 +42,8 @@ dependencies {
implementation libs.androidx.compose.material
implementation libs.androidx.core
implementation libs.kotlin
- implementation libs.maps.playservice
- implementation libs.maps.ktx.std
- implementation libs.maps.ktx.utils
+ api libs.maps.ktx.std
+ api libs.maps.ktx.utils
testImplementation libs.test.junit
androidTestImplementation platform(libs.androidx.compose.bom)
diff --git a/maps-compose/build.gradle b/maps-compose/build.gradle
index 5da3074a3..e6a8343a2 100644
--- a/maps-compose/build.gradle
+++ b/maps-compose/build.gradle
@@ -39,8 +39,7 @@ dependencies {
implementation libs.androidx.core
implementation libs.androidx.compose.foundation
implementation libs.kotlin
- implementation libs.maps.playservice
- implementation libs.maps.ktx.std
+ api libs.maps.ktx.std
testImplementation libs.test.junit
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/GoogleMap.kt b/maps-compose/src/main/java/com/google/maps/android/compose/GoogleMap.kt
index 63ef9b015..aaa6d252e 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/GoogleMap.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/GoogleMap.kt
@@ -20,7 +20,6 @@ import android.location.Location
import android.os.Bundle
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Row
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.CompositionContext
@@ -89,7 +88,6 @@ public fun GoogleMap(
onMyLocationClick: ((Location) -> Unit)? = null,
onPOIClick: ((PointOfInterest) -> Unit)? = null,
contentPadding: PaddingValues = NoPadding,
- myLocationButton: (@Composable @GoogleMapComposable () -> Unit)? = null,
content: (@Composable @GoogleMapComposable () -> Unit)? = null,
) {
// When in preview, early return a Box with the received modifier preserving layout
@@ -134,16 +132,11 @@ public fun GoogleMap(
val currentContentPadding by rememberUpdatedState(contentPadding)
// If we pass a custom location button, the native one is deactivated.
- val currentUiSettings by rememberUpdatedState(if (myLocationButton != null) {
- uiSettings.copy(myLocationButtonEnabled = false)
- } else {
- uiSettings
- })
+ val currentUiSettings by rememberUpdatedState(uiSettings)
val currentMapProperties by rememberUpdatedState(properties)
val parentComposition = rememberCompositionContext()
val currentContent by rememberUpdatedState(content)
- val currentLocation by rememberUpdatedState(myLocationButton)
LaunchedEffect(Unit) {
disposingComposition {
@@ -165,10 +158,6 @@ public fun GoogleMap(
}
}
}
- Row(modifier = modifier) {
- currentLocation?.invoke()
- }
-
}
internal suspend inline fun disposingComposition(factory: () -> Composition) {
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/Marker.kt b/maps-compose/src/main/java/com/google/maps/android/compose/Marker.kt
index 5412e917b..eff2e37e9 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/Marker.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/Marker.kt
@@ -14,6 +14,7 @@
package com.google.maps.android.compose
+import android.view.View
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ComposeNode
import androidx.compose.runtime.CompositionContext
@@ -27,9 +28,12 @@ import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
+import com.google.android.gms.maps.model.AdvancedMarkerOptions
import com.google.android.gms.maps.model.BitmapDescriptor
+import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Marker
+import com.google.android.gms.maps.model.PinConfig
import com.google.maps.android.ktx.addMarker
internal class MarkerNode(
@@ -46,6 +50,7 @@ internal class MarkerNode(
override fun onAttached() {
markerState.marker = marker
}
+
override fun onRemoved() {
markerState.marker = null
marker.remove()
@@ -514,3 +519,204 @@ private fun MarkerImpl(
}
)
}
+
+
+/**
+ * A composable for an advanced marker on the map.
+ *
+ * @param state the [MarkerState] to be used to control or observe the marker
+ * state such as its position and info window
+ * @param alpha the alpha (opacity) of the marker
+ * @param anchor the anchor for the marker image
+ * @param draggable sets the draggability for the marker
+ * @param flat sets if the marker should be flat against the map
+ * @param infoWindowAnchor the anchor point of the info window on the marker image
+ * @param rotation the rotation of the marker in degrees clockwise about the marker's anchor point
+ * @param snippet the snippet for the marker
+ * @param tag optional tag to associate with the marker
+ * @param title the title for the marker
+ * @param visible the visibility of the marker
+ * @param zIndex the z-index of the marker
+ * @param onClick a lambda invoked when the marker is clicked
+ * @param onInfoWindowClick a lambda invoked when the marker's info window is clicked
+ * @param onInfoWindowClose a lambda invoked when the marker's info window is closed
+ * @param onInfoWindowLongClick a lambda invoked when the marker's info window is long clicked
+ * @param pinConfig the PinConfig object that will be used for the advanced marker
+ * @param iconView the custom view to be used on the advanced marker
+ * @param collisionBehavior the expected collision behavior
+ */
+@Composable
+@GoogleMapComposable
+public fun AdvancedMarker(
+ state: MarkerState = rememberMarkerState(),
+ alpha: Float = 1.0f,
+ anchor: Offset = Offset(0.5f, 1.0f),
+ draggable: Boolean = false,
+ flat: Boolean = false,
+ infoWindowAnchor: Offset = Offset(0.5f, 0.0f),
+ rotation: Float = 0.0f,
+ snippet: String? = null,
+ tag: Any? = null,
+ title: String? = null,
+ visible: Boolean = true,
+ zIndex: Float = 0.0f,
+ onClick: (Marker) -> Boolean = { false },
+ onInfoWindowClick: (Marker) -> Unit = {},
+ onInfoWindowClose: (Marker) -> Unit = {},
+ onInfoWindowLongClick: (Marker) -> Unit = {},
+ pinConfig: PinConfig? = null,
+ iconView: View? = null,
+ collisionBehavior: Int = AdvancedMarkerOptions.CollisionBehavior.REQUIRED
+) {
+
+ AdvancedMarkerImpl(
+ state = state,
+ alpha = alpha,
+ anchor = anchor,
+ draggable = draggable,
+ flat = flat,
+ infoWindowAnchor = infoWindowAnchor,
+ rotation = rotation,
+ snippet = snippet,
+ tag = tag,
+ title = title,
+ visible = visible,
+ zIndex = zIndex,
+ onClick = onClick,
+ onInfoWindowClick = onInfoWindowClick,
+ onInfoWindowClose = onInfoWindowClose,
+ onInfoWindowLongClick = onInfoWindowLongClick,
+ pinConfig = pinConfig,
+ iconView = iconView,
+ collisionBehavior = collisionBehavior
+ )
+}
+
+/**
+ * Internal implementation for an advanced marker on a Google map.
+ *
+ * @param state the [MarkerState] to be used to control or observe the marker
+ * state such as its position and info window
+ * @param alpha the alpha (opacity) of the marker
+ * @param anchor the anchor for the marker image
+ * @param draggable sets the draggability for the marker
+ * @param flat sets if the marker should be flat against the map
+ * @param infoWindowAnchor the anchor point of the info window on the marker image
+ * @param rotation the rotation of the marker in degrees clockwise about the marker's anchor point
+ * @param snippet the snippet for the marker
+ * @param tag optional tag to associate with the marker
+ * @param title the title for the marker
+ * @param visible the visibility of the marker
+ * @param zIndex the z-index of the marker
+ * @param onClick a lambda invoked when the marker is clicked
+ * @param onInfoWindowClick a lambda invoked when the marker's info window is clicked
+ * @param onInfoWindowClose a lambda invoked when the marker's info window is closed
+ * @param onInfoWindowLongClick a lambda invoked when the marker's info window is long clicked
+ * @param infoWindow optional composable lambda expression for customizing
+ * the entire info window. If this value is non-null, the value in infoContent]
+ * will be ignored.
+ * @param infoContent optional composable lambda expression for customizing
+ * the info window's content. If this value is non-null, [infoWindow] must be null.
+ * @param pinConfig the PinConfig object that will be used for the advanced marker
+ * @param iconView the custom view to be used on the advanced marker
+ * @param collisionBehavior the expected collision behavior
+ */
+@Composable
+@GoogleMapComposable
+private fun AdvancedMarkerImpl(
+
+ state: MarkerState = rememberMarkerState(),
+ alpha: Float = 1.0f,
+ anchor: Offset = Offset(0.5f, 1.0f),
+ draggable: Boolean = false,
+ flat: Boolean = false,
+ infoWindowAnchor: Offset = Offset(0.5f, 0.0f),
+ rotation: Float = 0.0f,
+ snippet: String? = null,
+ tag: Any? = null,
+ title: String? = null,
+ visible: Boolean = true,
+ zIndex: Float = 0.0f,
+ onClick: (Marker) -> Boolean = { false },
+ onInfoWindowClick: (Marker) -> Unit = {},
+ onInfoWindowClose: (Marker) -> Unit = {},
+ onInfoWindowLongClick: (Marker) -> Unit = {},
+ infoWindow: (@Composable (Marker) -> Unit)? = null,
+ infoContent: (@Composable (Marker) -> Unit)? = null,
+ pinConfig: PinConfig? = null,
+ iconView: View? = null,
+ collisionBehavior: Int = AdvancedMarkerOptions.CollisionBehavior.REQUIRED
+) {
+
+ val mapApplier = currentComposer.applier as? MapApplier
+ val compositionContext = rememberCompositionContext()
+
+ val advancedMarkerOptions = AdvancedMarkerOptions()
+ .position(state.position)
+ .collisionBehavior(collisionBehavior)
+ if (iconView != null) {
+ advancedMarkerOptions.iconView(iconView)
+ } else if (pinConfig != null) {
+ advancedMarkerOptions.icon(BitmapDescriptorFactory.fromPinConfig(pinConfig))
+ }
+
+ ComposeNode(
+ factory = {
+ val marker = mapApplier?.map?.addMarker(advancedMarkerOptions)
+ ?: error("Error adding marker")
+ marker.tag = tag
+ MarkerNode(
+ compositionContext = compositionContext,
+ marker = marker,
+ markerState = state,
+ onMarkerClick = onClick,
+ onInfoWindowClick = onInfoWindowClick,
+ onInfoWindowClose = onInfoWindowClose,
+ onInfoWindowLongClick = onInfoWindowLongClick,
+ infoContent = infoContent,
+ infoWindow = infoWindow,
+ )
+ },
+ update = {
+ update(onClick) { this.onMarkerClick = it }
+ update(onInfoWindowClick) { this.onInfoWindowClick = it }
+ update(onInfoWindowClose) { this.onInfoWindowClose = it }
+ update(onInfoWindowLongClick) { this.onInfoWindowLongClick = it }
+ update(infoContent) { this.infoContent = it }
+ update(infoWindow) { this.infoWindow = it }
+
+ set(alpha) { this.marker.alpha = it }
+ set(anchor) { this.marker.setAnchor(it.x, it.y) }
+ set(draggable) { this.marker.isDraggable = it }
+ set(flat) { this.marker.isFlat = it }
+ set(infoWindowAnchor) { this.marker.setInfoWindowAnchor(it.x, it.y) }
+ set(state.position) { this.marker.position = it }
+ set(rotation) { this.marker.rotation = it }
+ set(snippet) {
+ this.marker.snippet = it
+ if (this.marker.isInfoWindowShown) {
+ this.marker.showInfoWindow()
+ }
+ }
+ set(tag) { this.marker.tag = it }
+ set(title) {
+ this.marker.title = it
+ if (this.marker.isInfoWindowShown) {
+ this.marker.showInfoWindow()
+ }
+ }
+ set(pinConfig) {
+ if (iconView == null) {
+ this.marker.setIcon(pinConfig?.let { it1 ->
+ BitmapDescriptorFactory.fromPinConfig(
+ it1
+ )
+ })
+ }
+ }
+
+ set(visible) { this.marker.isVisible = it }
+ set(zIndex) { this.marker.zIndex = it }
+ }
+ )
+}
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/streetview/StreetView.kt b/maps-compose/src/main/java/com/google/maps/android/compose/streetview/StreetView.kt
index cc774f4c0..e73c7d53f 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/streetview/StreetView.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/streetview/StreetView.kt
@@ -1,3 +1,17 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package com.google.maps.android.compose.streetview
import android.content.ComponentCallbacks
@@ -23,14 +37,17 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import com.google.android.gms.maps.StreetViewPanoramaOptions
import com.google.android.gms.maps.StreetViewPanoramaView
-import com.google.android.gms.maps.model.StreetViewPanoramaCamera
import com.google.android.gms.maps.model.StreetViewPanoramaOrientation
import com.google.maps.android.compose.disposingComposition
import com.google.maps.android.ktx.MapsExperimentalFeature
import com.google.maps.android.ktx.awaitStreetViewPanorama
/**
- * A composable for displaying a Street View for a given location.
+ * A composable for displaying a Street View for a given location. A location might not be available for a given
+ * set of coordinates. We recommend you to check our sample on [StreetViewActivity] using our utility function
+ * in [StreetViewUtils] to manage non-existing locations.
+ *
+ *
*
* @param modifier Modifier to be applied to the StreetView
* @param cameraPositionState the [StreetViewCameraPositionState] to be used to control or observe