diff --git a/maps-app/src/androidTest/java/com/google/maps/android/compose/RecompositionActivityTests.kt b/maps-app/src/androidTest/java/com/google/maps/android/compose/RecompositionActivityTests.kt new file mode 100644 index 00000000..fc5d4e2b --- /dev/null +++ b/maps-app/src/androidTest/java/com/google/maps/android/compose/RecompositionActivityTests.kt @@ -0,0 +1,68 @@ +// Copyright 2026 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 + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import com.google.android.gms.maps.model.CameraPosition +import org.junit.Assert.assertNotEquals +import org.junit.Rule +import org.junit.Test + +class RecompositionActivityTests { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun testChangeLocationButton_MovesCameraToNewLocation() { + check(hasValidApiKey) { "Maps API key not specified" } + val initialPosition = singapore + val cameraPositionState = CameraPositionState( + position = CameraPosition.fromLatLngZoom(initialPosition, 11f) + ) + + composeTestRule.setContent { + RecompositionActivity().GoogleMapView( + modifier = Modifier.fillMaxSize(), + cameraPositionState = cameraPositionState + ) + } + + // Verify initial camera target is centered at Singapore + initialPosition.assertEquals(cameraPositionState.position.target) + + // Click the "Change Location" button + composeTestRule.onNodeWithText("Change Location") + .assertIsDisplayed() + .performClick() + + // Wait until the camera target moves away from the initial position + composeTestRule.waitUntil(timeout5) { + cameraPositionState.position.target != initialPosition + } + + // Wait until the camera animation has completed + composeTestRule.waitUntil(timeout5) { + !cameraPositionState.isMoving + } + + // Verify that the camera has actually moved to a new location + assertNotEquals(initialPosition, cameraPositionState.position.target) + } +} diff --git a/maps-app/src/main/java/com/google/maps/android/compose/RecompositionActivity.kt b/maps-app/src/main/java/com/google/maps/android/compose/RecompositionActivity.kt index 1de9dc2a..c01d44df 100644 --- a/maps-app/src/main/java/com/google/maps/android/compose/RecompositionActivity.kt +++ b/maps-app/src/main/java/com/google/maps/android/compose/RecompositionActivity.kt @@ -29,12 +29,17 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.model.Marker import com.google.maps.android.compose.theme.MapsComposeSampleTheme -import kotlin.random.Random +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch private const val TAG = "RecompositionActivity" +private val singaporeLocations = listOf(singapore, singapore2, singapore3) /** * This is a sample activity showcasing how the recomposition works. The location is changed @@ -71,11 +76,11 @@ class RecompositionActivity : ComponentActivity() { content: @Composable () -> Unit = {}, ) { val markerState = rememberUpdatedMarkerState(position = singapore) + val coroutineScope = rememberCoroutineScope() + var animationJob by remember { mutableStateOf(null) } - val uiSettings by remember { mutableStateOf(MapUiSettings(compassEnabled = false)) } - val mapProperties by remember { - mutableStateOf(MapProperties(mapType = MapType.NORMAL)) - } + val uiSettings = remember { MapUiSettings(compassEnabled = false) } + val mapProperties = remember { MapProperties(mapType = MapType.NORMAL) } val mapVisible by remember { mutableStateOf(true) } if (mapVisible) { @@ -88,12 +93,14 @@ class RecompositionActivity : ComponentActivity() { Log.d(TAG, "POI clicked: ${it.name}") } ) { - val markerClick: (Marker) -> Boolean = { - Log.d(TAG, "${it.title} was clicked") - cameraPositionState.projection?.let { projection -> - Log.d(TAG, "The current projection is: $projection") + val markerClick: (Marker) -> Boolean = remember(cameraPositionState) { + { marker -> + Log.d(TAG, "${marker.title} was clicked") + cameraPositionState.projection?.let { projection -> + Log.d(TAG, "The current projection is: $projection") + } + false } - false } Marker( @@ -106,12 +113,13 @@ class RecompositionActivity : ComponentActivity() { } Column { Button(onClick = { - val randomValue = Random.nextInt(3) - markerState.position = when (randomValue) { - 0 -> singapore - 1 -> singapore2 - 2 -> singapore3 - else -> singapore + val newPosition = (singaporeLocations - markerState.position).random() + markerState.position = newPosition + animationJob?.cancel() + animationJob = coroutineScope.launch { + cameraPositionState.animate( + CameraUpdateFactory.newLatLng(newPosition) + ) } }) { Text("Change Location")