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 eb61fdd2..80ee4a3a 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 @@ -101,7 +101,7 @@ class GoogleMapViewTests { composeTestRule.waitUntil(1000) { cameraPositionState.isMoving } - composeTestRule.waitUntil(1000) { + composeTestRule.waitUntil(3000) { !cameraPositionState.isMoving } assertEquals( @@ -118,7 +118,7 @@ class GoogleMapViewTests { composeTestRule.waitUntil(1000) { cameraPositionState.isMoving } - composeTestRule.waitUntil(1000) { + composeTestRule.waitUntil(3000) { !cameraPositionState.isMoving } assertEquals( @@ -135,7 +135,7 @@ class GoogleMapViewTests { composeTestRule.waitUntil(1000) { cameraPositionState.isMoving } - composeTestRule.waitUntil(1000) { + composeTestRule.waitUntil(3000) { !cameraPositionState.isMoving } assertEquals( @@ -184,6 +184,30 @@ class GoogleMapViewTests { ) } + @Test(expected = IllegalStateException::class) + fun testMarkerStateCannotBeReused() { + val countDownLatch = CountDownLatch(1) + composeTestRule.setContent { + GoogleMap( + modifier = Modifier.fillMaxSize(), + cameraPositionState = cameraPositionState, + onMapLoaded = { + countDownLatch.countDown() + } + ) { + val markerState = rememberMarkerState() + Marker( + state = markerState + ) + Marker( + state = markerState + ) + } + } + val mapLoaded = countDownLatch.await(30, TimeUnit.SECONDS) + assertTrue(mapLoaded) + } + private fun zoom( shouldAnimate: Boolean, zoomIn: Boolean, diff --git a/app/src/main/java/com/google/maps/android/compose/MapSampleActivity.kt b/app/src/main/java/com/google/maps/android/compose/MapSampleActivity.kt index 80eff065..36051f23 100644 --- a/app/src/main/java/com/google/maps/android/compose/MapSampleActivity.kt +++ b/app/src/main/java/com/google/maps/android/compose/MapSampleActivity.kt @@ -109,11 +109,12 @@ fun GoogleMapView( cameraPositionState: CameraPositionState, onMapLoaded: () -> Unit, ) { - val singaporePositionState = rememberMarkerPositionState(position = singapore) - val singapore2PositionState = rememberMarkerPositionState(position = singapore2) - var circlePositionState by remember { mutableStateOf(singapore) } - if (singaporePositionState.dragState == DragState.END) { - circlePositionState = singaporePositionState.position + val singaporeState = rememberMarkerState(position = singapore) + val singapore2State = rememberMarkerState(position = singapore2) + val singapore3State = rememberMarkerState(position = singapore3) + var circleCenter by remember { mutableStateOf(singapore) } + if (singaporeState.dragState == DragState.END) { + circleCenter = singaporeState.position } var uiSettings by remember { mutableStateOf(MapUiSettings(compassEnabled = false)) } @@ -142,7 +143,7 @@ fun GoogleMapView( false } MarkerInfoWindowContent( - positionState = singaporePositionState, + state = singaporeState, title = "Zoom in has been tapped $ticker times.", onClick = markerClick, draggable = true, @@ -150,7 +151,7 @@ fun GoogleMapView( Text(it.title ?: "Title", color = Color.Red) } MarkerInfoWindowContent( - positionState = singapore2PositionState, + state = singapore2State, title = "Marker with custom info window.\nZoom in has been tapped $ticker times.", icon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE), onClick = markerClick, @@ -158,12 +159,12 @@ fun GoogleMapView( Text(it.title ?: "Title", color = Color.Blue) } Marker( - positionState = MarkerPositionState(position = singapore3), + state = singapore3State, title = "Marker in Singapore", onClick = markerClick ) Circle( - center = circlePositionState, + center = circleCenter, fillColor = MaterialTheme.colors.secondary, strokeColor = MaterialTheme.colors.secondaryVariant, radius = 1000.0, @@ -184,7 +185,8 @@ fun GoogleMapView( onClick = { mapProperties = mapProperties.copy(mapType = MapType.NORMAL) cameraPositionState.position = defaultCameraPosition - singaporePositionState.position = singapore + singaporeState.position = singapore + singaporeState.hideInfoWindow() } ) { Text(text = "RESET MAP", style = MaterialTheme.typography.body1) @@ -219,7 +221,7 @@ fun GoogleMapView( uiSettings = uiSettings.copy(zoomControlsEnabled = it) } ) - DebugView(cameraPositionState, singaporePositionState) + DebugView(cameraPositionState, singaporeState) } } @@ -298,7 +300,7 @@ private fun MapButton(text: String, onClick: () -> Unit) { @Composable private fun DebugView( cameraPositionState: CameraPositionState, - markerPositionState: MarkerPositionState + markerState: MarkerState ) { Column( Modifier @@ -311,8 +313,8 @@ private fun DebugView( Text(text = "Camera position is ${cameraPositionState.position}") Spacer(modifier = Modifier.height(4.dp)) val dragging = - if (markerPositionState.dragState == DragState.DRAG) "dragging" else "not dragging" + if (markerState.dragState == DragState.DRAG) "dragging" else "not dragging" Text(text = "Marker is $dragging") - Text(text = "Marker position is ${markerPositionState.position}") + Text(text = "Marker position is ${markerState.position}") } } diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/MapApplier.kt b/maps-compose/src/main/java/com/google/maps/android/compose/MapApplier.kt index 6bda4315..09b68361 100644 --- a/maps-compose/src/main/java/com/google/maps/android/compose/MapApplier.kt +++ b/maps-compose/src/main/java/com/google/maps/android/compose/MapApplier.kt @@ -112,22 +112,22 @@ internal class MapApplier( map.setOnMarkerDragListener(object : GoogleMap.OnMarkerDragListener { override fun onMarkerDrag(marker: Marker) { with(decorations.nodeForMarker(marker)) { - this?.markerPositionState?.position = marker.position - this?.markerPositionState?.dragState = DragState.DRAG + this?.markerState?.position = marker.position + this?.markerState?.dragState = DragState.DRAG } } override fun onMarkerDragEnd(marker: Marker) { with(decorations.nodeForMarker(marker)) { - this?.markerPositionState?.position = marker.position - this?.markerPositionState?.dragState = DragState.END + this?.markerState?.position = marker.position + this?.markerState?.dragState = DragState.END } } override fun onMarkerDragStart(marker: Marker) { with(decorations.nodeForMarker(marker)) { - this?.markerPositionState?.position = marker.position - this?.markerPositionState?.dragState = DragState.START + this?.markerState?.position = marker.position + this?.markerState?.dragState = DragState.START } } }) 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 6a70011a..920c362f 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 @@ -34,7 +34,7 @@ import com.google.maps.android.ktx.addMarker internal class MarkerNode( val compositionContext: CompositionContext, val marker: Marker, - val markerPositionState: MarkerPositionState, + val markerState: MarkerState, var onMarkerClick: (Marker) -> Boolean, var onInfoWindowClick: (Marker) -> Unit, var onInfoWindowClose: (Marker) -> Unit, @@ -42,7 +42,11 @@ internal class MarkerNode( var infoWindow: (@Composable (Marker) -> Unit)?, var infoContent: (@Composable (Marker) -> Unit)?, ) : MapNode { + override fun onAttached() { + markerState.marker = marker + } override fun onRemoved() { + markerState.marker = null marker.remove() } } @@ -57,7 +61,7 @@ enum class DragState { * * @param position the initial marker position */ -class MarkerPositionState( +class MarkerState( position: LatLng = LatLng(0.0, 0.0) ) { /** @@ -71,30 +75,54 @@ class MarkerPositionState( var dragState: DragState by mutableStateOf(DragState.END) internal set + // The marker associated with this MarkerState. + internal var marker: Marker? = null + set(value) { + if (field == null && value == null) return + if (field != null && value != null) { + error("MarkerState may only be associated with one Marker at a time.") + } + field = value + } + + /** + * Shows the info window for the underlying marker + */ + fun showInfoWindow() { + marker?.showInfoWindow() + } + + /** + * Hides the info window for the underlying marker + */ + fun hideInfoWindow() { + marker?.hideInfoWindow() + } + companion object { /** - * The default saver implementation for [MarkerPositionState] + * The default saver implementation for [MarkerState] */ - val Saver = Saver( + val Saver = Saver( save = { it.position }, - restore = { MarkerPositionState(it) } + restore = { MarkerState(it) } ) } } @Composable -fun rememberMarkerPositionState( +fun rememberMarkerState( key: String? = null, position: LatLng = LatLng(0.0, 0.0) -): MarkerPositionState = rememberSaveable(key = key, saver = MarkerPositionState.Saver) { - MarkerPositionState(position) +): MarkerState = rememberSaveable(key = key, saver = MarkerState.Saver) { + MarkerState(position) } /** * A composable for a marker on the map. * - * @param positionState the [MarkerPositionState] to be used to control or observe the marker - * position state + * @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 @@ -114,7 +142,7 @@ fun rememberMarkerPositionState( */ @Composable fun Marker( - positionState: MarkerPositionState = rememberMarkerPositionState(), + state: MarkerState = rememberMarkerState(), alpha: Float = 1.0f, anchor: Offset = Offset(0.5f, 1.0f), draggable: Boolean = false, @@ -133,7 +161,7 @@ fun Marker( onInfoWindowLongClick: (Marker) -> Unit = {}, ) { MarkerImpl( - positionState = positionState, + state = state, alpha = alpha, anchor = anchor, draggable = draggable, @@ -158,8 +186,8 @@ fun Marker( * customized. If this customization is not required, use * [com.google.maps.android.compose.Marker]. * - * @param positionState the [MarkerPositionState] to be used to control or observe the marker - * position state + * @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 @@ -181,7 +209,7 @@ fun Marker( */ @Composable fun MarkerInfoWindow( - positionState: MarkerPositionState = rememberMarkerPositionState(), + state: MarkerState = rememberMarkerState(), alpha: Float = 1.0f, anchor: Offset = Offset(0.5f, 1.0f), draggable: Boolean = false, @@ -201,7 +229,7 @@ fun MarkerInfoWindow( content: (@Composable (Marker) -> Unit)? = null ) { MarkerImpl( - positionState = positionState, + state = state, alpha = alpha, anchor = anchor, draggable = draggable, @@ -227,8 +255,8 @@ fun MarkerInfoWindow( * customized. If this customization is not required, use * [com.google.maps.android.compose.Marker]. * - * @param positionState the [MarkerPositionState] to be used to control or observe the marker - * position state + * @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 @@ -250,7 +278,7 @@ fun MarkerInfoWindow( */ @Composable fun MarkerInfoWindowContent( - positionState: MarkerPositionState = rememberMarkerPositionState(), + state: MarkerState = rememberMarkerState(), alpha: Float = 1.0f, anchor: Offset = Offset(0.5f, 1.0f), draggable: Boolean = false, @@ -270,7 +298,7 @@ fun MarkerInfoWindowContent( content: (@Composable (Marker) -> Unit)? = null ) { MarkerImpl( - positionState = positionState, + state = state, alpha = alpha, anchor = anchor, draggable = draggable, @@ -294,8 +322,8 @@ fun MarkerInfoWindowContent( /** * Internal implementation for a marker on a Google map. * - * @param positionState the [MarkerPositionState] to be used to control or observe the marker - * position state + * @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 @@ -320,7 +348,7 @@ fun MarkerInfoWindowContent( */ @Composable private fun MarkerImpl( - positionState: MarkerPositionState = rememberMarkerPositionState(), + state: MarkerState = rememberMarkerState(), alpha: Float = 1.0f, anchor: Offset = Offset(0.5f, 1.0f), draggable: Boolean = false, @@ -351,7 +379,7 @@ private fun MarkerImpl( flat(flat) icon(icon) infoWindowAnchor(infoWindowAnchor.x, infoWindowAnchor.y) - position(positionState.position) + position(state.position) rotation(rotation) snippet(snippet) title(title) @@ -362,7 +390,7 @@ private fun MarkerImpl( MarkerNode( compositionContext = compositionContext, marker = marker, - markerPositionState = positionState, + markerState = state, onMarkerClick = onClick, onInfoWindowClick = onInfoWindowClick, onInfoWindowClose = onInfoWindowClose, @@ -385,7 +413,7 @@ private fun MarkerImpl( set(flat) { this.marker.isFlat = it } set(icon) { this.marker.setIcon(it) } set(infoWindowAnchor) { this.marker.setInfoWindowAnchor(it.x, it.y) } - set(positionState.position) { this.marker.position = it } + set(state.position) { this.marker.position = it } set(rotation) { this.marker.rotation = it } set(snippet) { this.marker.snippet = it