From 99646dd8f90ac5d49858e95206089019339abba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20L=C3=B3pez=20Ma=C3=B1as?= Date: Fri, 20 Oct 2023 18:14:54 +0200 Subject: [PATCH 1/4] chore: increase timeout times in tests (#430) --- .../android/compose/GoogleMapViewTests.kt | 22 +++++++++---------- .../maps/android/compose/StreetViewTests.kt | 2 +- .../google/maps/android/compose/TestUtils.kt | 3 +++ 3 files changed, 15 insertions(+), 12 deletions(-) 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" From f7a2e18470fb5ed0e3ce1846f7b8a431c952bfe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20L=C3=B3pez=20Ma=C3=B1as?= Date: Wed, 25 Oct 2023 19:54:32 +0200 Subject: [PATCH 2/4] chore: demonstrate using StreetViewUtils to avoid crashes when adding a Street View composable (#435) * chore: add StreetViewUtil sample * doc: add documentation --------- Co-authored-by: Angela Yu <5506675+wangela@users.noreply.github.com> --- README.md | 43 ++++++++--- .../android/compose/StreetViewActivity.kt | 73 ++++++++++++------- .../android/compose/streetview/StreetView.kt | 21 +++++- 3 files changed, 100 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index e31848ce6..2109f442d 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,9 @@ 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. + + // Optionally, you can include the Compose utils library for Clustering, + // Street View metadata checks, etc. implementation 'com.google.maps.android:maps-compose-utils:3.1.1' // Optionally, you can include the widgets library for ScaleBar, etc. @@ -230,7 +231,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,9 +274,9 @@ 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 @@ -289,7 +299,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 +329,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 +353,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. 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/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 From cebb6a0a5f9f66d445c02abd80f5ca31d4f60355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20L=C3=B3pez=20Ma=C3=B1as?= Date: Thu, 26 Oct 2023 00:30:51 +0200 Subject: [PATCH 3/4] feat!: setting the custom location button via sample (#428) * feat: adding custom location button to sample * doc: added explanatory comment BREAKING CHANGE: remove `myLocationButton` from `GoogleMap`. Equivalent functionality for customizing a map control is demonstrated in the sample app. --- .../android/compose/CustomControlsActivity.kt | 26 ++++++++++--------- .../google/maps/android/compose/GoogleMap.kt | 13 +--------- 2 files changed, 15 insertions(+), 24 deletions(-) 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/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 da27b1225..8aae82d1f 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 @@ -88,7 +87,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 @@ -119,16 +117,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 { @@ -150,10 +143,6 @@ public fun GoogleMap( } } } - Row(modifier = modifier) { - currentLocation?.invoke() - } - } internal suspend inline fun disposingComposition(factory: () -> Composition) { From d299486d3298cb6955ef1a857cc2b97e5d421990 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 25 Oct 2023 22:38:16 +0000 Subject: [PATCH 4/4] chore(release): 4.0.0 [skip ci] # [4.0.0](https://github.com/googlemaps/android-maps-compose/compare/v3.1.1...v4.0.0) (2023-10-25) * feat!: setting the custom location button via sample (#428) ([cebb6a0](https://github.com/googlemaps/android-maps-compose/commit/cebb6a0a5f9f66d445c02abd80f5ca31d4f60355)), closes [#428](https://github.com/googlemaps/android-maps-compose/issues/428) ### BREAKING CHANGES * remove `myLocationButton` from `GoogleMap`. Equivalent functionality for customizing a map control is demonstrated in the sample app. --- README.md | 6 +++--- build.gradle | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2109f442d..f29f9f5e7 100644 --- a/README.md +++ b/README.md @@ -22,14 +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' + implementation 'com.google.maps.android:maps-compose:4.0.0' // Optionally, you can include the Compose utils library for Clustering, // Street View metadata checks, etc. - implementation 'com.google.maps.android:maps-compose-utils:3.1.1' + implementation 'com.google.maps.android:maps-compose-utils:4.0.0' // 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.0.0' } ``` diff --git a/build.gradle b/build.gradle index f2e3bec67..98be586a4 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.0.0' project.ext.artifactId = rootProject.ext.projectArtifactId(project) }