diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c3b5aa637..0063f72db 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -48,6 +48,9 @@
+
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 42ef0c0e7..ea0c1a17b 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
@@ -102,6 +102,18 @@ class MainActivity : ComponentActivity() {
Text(getString(R.string.map_in_column_activity))
}
Spacer(modifier = Modifier.padding(5.dp))
+ Button(
+ onClick = {
+ context.startActivity(
+ Intent(
+ context,
+ MapsInLazyColumnActivity::class.java
+ )
+ )
+ }) {
+ Text(getString(R.string.maps_in_lazy_column_activity))
+ }
+ Spacer(modifier = Modifier.padding(5.dp))
Button(
onClick = {
context.startActivity(
diff --git a/app/src/main/java/com/google/maps/android/compose/MapsInLazyColumnActivity.kt b/app/src/main/java/com/google/maps/android/compose/MapsInLazyColumnActivity.kt
new file mode 100644
index 000000000..c53d3b6b1
--- /dev/null
+++ b/app/src/main/java/com/google/maps/android/compose/MapsInLazyColumnActivity.kt
@@ -0,0 +1,260 @@
+package com.google.maps.android.compose
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.material.Card
+import androidx.compose.material.CircularProgressIndicator
+import androidx.compose.material.LocalTextStyle
+import androidx.compose.material.ProvideTextStyle
+import androidx.compose.material.Text
+import androidx.compose.material.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.google.android.gms.maps.GoogleMap
+import com.google.android.gms.maps.model.CameraPosition
+import com.google.android.gms.maps.model.IndoorBuilding
+import com.google.android.gms.maps.model.LatLng
+
+private data class CountryLocation(val name: String, val latLng: LatLng, val zoom: Float)
+
+private typealias MapItemId = String
+
+// From https://developers.google.com/public-data/docs/canonical/countries_csv
+private val countries = listOf(
+ CountryLocation("Hong Kong", LatLng(22.396428, 114.109497), 5f),
+ CountryLocation("Madison Square Garden (has indoor mode)", LatLng(40.7504656, -73.9937246), 19.33f),
+ CountryLocation("Bolivia", LatLng(-16.290154, -63.588653), 5f),
+ CountryLocation("Ecuador", LatLng(-1.831239, -78.183406), 5f),
+ CountryLocation("Sweden", LatLng(60.128161, 18.643501), 5f),
+ CountryLocation("Eritrea", LatLng(15.179384, 39.782334), 5f),
+ CountryLocation("Portugal", LatLng(39.399872, -8.224454), 5f),
+ CountryLocation("Belgium", LatLng(50.503887, 4.469936), 5f),
+ CountryLocation("Slovakia", LatLng(48.669026, 19.699024), 5f),
+ CountryLocation("El Salvador", LatLng(13.794185, -88.89653), 5f),
+ CountryLocation("Bhutan", LatLng(27.514162, 90.433601), 5f),
+ CountryLocation("Saint Lucia", LatLng(13.909444, -60.978893), 5f),
+ CountryLocation("Uganda", LatLng(1.373333, 32.290275), 5f),
+ CountryLocation("South Africa", LatLng(-30.559482, 22.937506), 5f),
+ CountryLocation("Spain", LatLng(40.463667, -3.74922), 5f),
+ CountryLocation("Georgia", LatLng(42.315407, 43.356892), 5f),
+ CountryLocation("Burundi", LatLng(-3.373056, 29.918886), 5f)
+)
+
+private data class MapListItem(
+ val title: String,
+ val location: LatLng,
+ val zoom: Float,
+ val id: MapItemId
+)
+
+private val allItems = countries.mapIndexed { index, country ->
+ MapListItem(country.name, country.latLng, country.zoom, "MapInLazyColumn#$index")
+}
+
+class MapsInLazyColumnActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ var showLazyColumn by rememberSaveable { mutableStateOf(true) }
+ var visibleItems by rememberSaveable { mutableStateOf(allItems) }
+
+ fun setItemCount(count: Int) {
+ visibleItems = allItems.take(count.coerceIn(0, allItems.size))
+ }
+
+ Column {
+ Row(
+ Modifier
+ .fillMaxWidth()
+ .horizontalScroll(rememberScrollState()),
+ horizontalArrangement = Arrangement.SpaceEvenly
+ ) {
+ TextButton(onClick = { setItemCount(0) }) {
+ Text(text = "Clear")
+ }
+ TextButton(onClick = { setItemCount(visibleItems.size - 1) }) {
+ Text(text = "Remove")
+ }
+ TextButton(onClick = { showLazyColumn = !showLazyColumn }) {
+ Text(text = if (showLazyColumn) "Hide" else "Show")
+ }
+ TextButton(onClick = { setItemCount(visibleItems.size + 1) }) {
+ Text(text = "Add")
+ }
+ TextButton(onClick = { setItemCount(allItems.size) }) {
+ Text(text = "Fill")
+ }
+ }
+ if (showLazyColumn) {
+ Box(Modifier.border(1.dp, Color.LightGray.copy(0.5f))) {
+ MapsInLazyColumn(visibleItems)
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun MapsInLazyColumn(mapItems: List) {
+ val lazyListState = rememberLazyListState()
+
+ val cameraPositionStates = mapItems.associate { item ->
+ item.id to rememberCameraPositionState(
+ key = item.id,
+ init = { position = CameraPosition.fromLatLngZoom(item.location, item.zoom) }
+ )
+ }
+ val visibleItemIds by remember(lazyListState) {
+ derivedStateOf {
+ lazyListState.layoutInfo.visibleItemsInfo.map { it.key as MapItemId }
+ }
+ }
+ val anyMapMoving by remember(cameraPositionStates) {
+ derivedStateOf {
+ visibleItemIds.any { cameraPositionStates[it]?.isMoving == true }
+ }
+ }
+
+ Box {
+ LazyColumn(
+ state = lazyListState,
+ userScrollEnabled = !anyMapMoving
+ ) {
+ items(mapItems, key = { it.id }) { item ->
+ val cameraPositionState = cameraPositionStates[item.id]!!
+
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .height(300.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ MapCard(item, cameraPositionState)
+ }
+ }
+ }
+ }
+}
+
+@OptIn(MapsComposeExperimentalApi::class)
+@Composable
+private fun MapCard(item: MapListItem, cameraPositionState: CameraPositionState) {
+ Card(
+ Modifier.padding(16.dp),
+ elevation = 4.dp
+ ) {
+ var mapLoaded by remember { mutableStateOf(false) }
+ var buildingFocused: Boolean? by remember { mutableStateOf(null) }
+ var focusedBuildingInvocationCount by remember { mutableIntStateOf(0) }
+ var activatedIndoorLevel: String? by remember { mutableStateOf(null) }
+ var activatedIndoorLevelInvocationCount by remember { mutableIntStateOf(0) }
+ var onMapClickCount by remember { mutableIntStateOf(0) }
+
+ var map: GoogleMap? by remember { mutableStateOf(null) }
+
+ fun updateIndoorLevel() {
+ activatedIndoorLevel = map!!.focusedBuilding?.run { levels.getOrNull(activeLevelIndex)?.name }
+ }
+
+ Box {
+ GoogleMap(
+ onMapClick = {
+ onMapClickCount++
+ },
+ properties = remember {
+ MapProperties(
+ isBuildingEnabled = true,
+ isIndoorEnabled = true
+ )
+ },
+ cameraPositionState = cameraPositionState,
+ onMapLoaded = { mapLoaded = true },
+ indoorStateChangeListener = object : IndoorStateChangeListener {
+ override fun onIndoorBuildingFocused() {
+ super.onIndoorBuildingFocused()
+ focusedBuildingInvocationCount++
+ buildingFocused = (map!!.focusedBuilding != null)
+ updateIndoorLevel()
+ }
+
+ override fun onIndoorLevelActivated(building: IndoorBuilding) {
+ super.onIndoorLevelActivated(building)
+ activatedIndoorLevelInvocationCount++
+ updateIndoorLevel()
+ }
+ }
+ ) {
+ MapEffect(Unit) { googleMap ->
+ map = googleMap
+ updateIndoorLevel()
+ buildingFocused = (googleMap.focusedBuilding != null)
+ }
+ }
+
+ AnimatedVisibility(!mapLoaded, enter = fadeIn(), exit = fadeOut()) {
+ Box(
+ Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+
+ @Composable
+ fun TextWithBackground(text: String, fontWeight: FontWeight = FontWeight.Medium) {
+ Text(
+ modifier = Modifier.background(Color.White.copy(0.7f)),
+ text = text,
+ fontWeight = fontWeight,
+ fontSize = 10.sp
+ )
+ }
+
+ Column(
+ modifier = Modifier.align(Alignment.BottomStart)
+ ) {
+ TextWithBackground(item.title, fontWeight = FontWeight.Bold)
+ TextWithBackground("Map loaded: $mapLoaded")
+ TextWithBackground("Map click count: $onMapClickCount")
+ TextWithBackground("Building focused: $buildingFocused")
+ TextWithBackground("Building focused invocation count: $focusedBuildingInvocationCount")
+ TextWithBackground("Indoor level: $activatedIndoorLevel")
+ TextWithBackground("Indoor level invocation count: $activatedIndoorLevelInvocationCount")
+ }
+ }
+ }
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6638f6ee6..3993bcbab 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -32,4 +32,5 @@
Street View
Custom Location Button
Accessibility
+ Maps in LazyColumn
\ No newline at end of file
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 df54fcd88..1c948c4c1 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
@@ -18,27 +18,29 @@ import android.content.ComponentCallbacks
import android.content.res.Configuration
import android.location.Location
import android.os.Bundle
+import android.view.View
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.CompositionContext
import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCompositionContext
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
-import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.findViewTreeLifecycleOwner
import com.google.android.gms.maps.GoogleMapOptions
import com.google.android.gms.maps.LocationSource
import com.google.android.gms.maps.MapView
@@ -46,7 +48,11 @@ import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MapColorScheme
import com.google.android.gms.maps.model.PointOfInterest
import com.google.maps.android.ktx.awaitMap
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.launch
/**
* A compose container for a [MapView].
@@ -100,12 +106,6 @@ public fun GoogleMap(
return
}
- val context = LocalContext.current
- val mapView = remember { MapView(context, googleMapOptionsFactory()) }
-
- AndroidView(modifier = modifier, factory = { mapView })
- MapLifecycle(mapView)
-
// rememberUpdatedState and friends are used here to make these values observable to
// the subcomposition without providing a new content function each recomposition
val mapClickListeners = remember { MapClickListeners() }.also {
@@ -117,125 +117,155 @@ public fun GoogleMap(
it.onMyLocationClick = onMyLocationClick
it.onPOIClick = onPOIClick
}
- val currentContentDescription by rememberUpdatedState(contentDescription)
- val currentLocationSource by rememberUpdatedState(locationSource)
- val currentCameraPositionState by rememberUpdatedState(cameraPositionState)
- val currentContentPadding by rememberUpdatedState(contentPadding)
- val currentUiSettings by rememberUpdatedState(uiSettings)
- val currentMapProperties by rememberUpdatedState(properties)
- val currentColorScheme by rememberUpdatedState(mapColorScheme)
+
+ val mapUpdaterState = remember {
+ MapUpdaterState(
+ mergeDescendants,
+ contentDescription,
+ cameraPositionState,
+ contentPadding,
+ locationSource,
+ properties,
+ uiSettings,
+ mapColorScheme?.value,
+ )
+ }.also {
+ it.mergeDescendants = mergeDescendants
+ it.contentDescription = contentDescription
+ it.cameraPositionState = cameraPositionState
+ it.contentPadding = contentPadding
+ it.locationSource = locationSource
+ it.mapProperties = properties
+ it.mapUiSettings = uiSettings
+ it.mapColorScheme = mapColorScheme?.value
+ }
val parentComposition = rememberCompositionContext()
val currentContent by rememberUpdatedState(content)
- LaunchedEffect(Unit) {
- disposingComposition {
- mapView.newComposition(parentComposition, mapClickListeners) {
- MapUpdater(
- mergeDescendants = mergeDescendants,
- contentDescription = currentContentDescription,
- cameraPositionState = currentCameraPositionState,
- contentPadding = currentContentPadding,
- locationSource = currentLocationSource,
- mapProperties = currentMapProperties,
- mapUiSettings = currentUiSettings,
- colorMapScheme = currentColorScheme?.value
- )
+ var subcompositionJob by remember { mutableStateOf(null) }
+ val parentCompositionScope = rememberCoroutineScope()
- MapClickListenerUpdater()
+ AndroidView(
+ modifier = modifier,
+ factory = { context ->
+ MapView(context, googleMapOptionsFactory()).also { mapView ->
+ val componentCallbacks = object : ComponentCallbacks {
+ override fun onConfigurationChanged(newConfig: Configuration) {}
+ override fun onLowMemory() { mapView.onLowMemory() }
+ }
+ context.registerComponentCallbacks(componentCallbacks)
- CompositionLocalProvider(
- LocalCameraPositionState provides currentCameraPositionState,
- currentContent
+ val lifecycleObserver = MapLifecycleEventObserver(mapView)
+
+ mapView.tag = MapTagData(componentCallbacks, lifecycleObserver)
+
+ // Only register for [lifecycleOwner]'s lifecycle events while MapView is attached
+ val onAttachStateListener = object : View.OnAttachStateChangeListener {
+ private var lifecycle: Lifecycle? = null
+
+ override fun onViewAttachedToWindow(mapView: View) {
+ lifecycle = mapView.findViewTreeLifecycleOwner()!!.lifecycle.also {
+ it.addObserver(lifecycleObserver)
+ }
+ }
+
+ override fun onViewDetachedFromWindow(v: View) {
+ lifecycle?.removeObserver(lifecycleObserver)
+ lifecycle = null
+ lifecycleObserver.moveToBaseState()
+ }
+ }
+
+ mapView.addOnAttachStateChangeListener(onAttachStateListener)
+ }
+ },
+ onReset = { /* View is detached. */ },
+ onRelease = { mapView ->
+ val (componentCallbacks, lifecycleObserver) = mapView.tagData
+ mapView.context.unregisterComponentCallbacks(componentCallbacks)
+ lifecycleObserver.moveToDestroyedState()
+ mapView.tag = null
+ },
+ update = { mapView ->
+ if (subcompositionJob == null) {
+ subcompositionJob = parentCompositionScope.launchSubcomposition(
+ mapUpdaterState,
+ parentComposition,
+ mapView,
+ mapClickListeners,
+ currentContent,
)
}
}
- }
-}
-
-internal suspend inline fun disposingComposition(factory: () -> Composition) {
- val composition = factory()
- try {
- awaitCancellation()
- } finally {
- composition.dispose()
- }
+ )
}
-private suspend inline fun MapView.newComposition(
- parent: CompositionContext,
+/**
+ * Create and apply the [content] compositions to the map +
+ * dispose the [Composition] when the parent composable is disposed.
+ * */
+private fun CoroutineScope.launchSubcomposition(
+ mapUpdaterState: MapUpdaterState,
+ parentComposition: CompositionContext,
+ mapView: MapView,
mapClickListeners: MapClickListeners,
- noinline content: @Composable () -> Unit
-): Composition {
- val map = awaitMap()
- return Composition(
- MapApplier(map, this, mapClickListeners), parent
- ).apply {
- setContent(content)
- }
-}
+ content: @Composable @GoogleMapComposable () -> Unit,
+): Job {
+ // Use [CoroutineStart.UNDISPATCHED] to kick off GoogleMap loading immediately
+ return launch(start = CoroutineStart.UNDISPATCHED) {
+ val map = mapView.awaitMap()
+ val composition = Composition(
+ applier = MapApplier(map, mapView, mapClickListeners),
+ parent = parentComposition
+ )
-/**
- * Registers lifecycle observers to the local [MapView].
- */
-@Composable
-private fun MapLifecycle(mapView: MapView) {
- val context = LocalContext.current
- val lifecycle = LocalLifecycleOwner.current.lifecycle
- val previousState = remember { mutableStateOf(Lifecycle.Event.ON_CREATE) }
- DisposableEffect(context, lifecycle, mapView) {
- val mapLifecycleObserver = mapView.lifecycleObserver(previousState)
- val callbacks = mapView.componentCallbacks()
-
- lifecycle.addObserver(mapLifecycleObserver)
- context.registerComponentCallbacks(callbacks)
-
- onDispose {
- lifecycle.removeObserver(mapLifecycleObserver)
- context.unregisterComponentCallbacks(callbacks)
- }
- }
- DisposableEffect(mapView) {
- onDispose {
- mapView.onDestroy()
- mapView.removeAllViews()
- }
- }
-}
+ try {
+ composition.setContent {
+ MapUpdater(mapUpdaterState)
-private fun MapView.lifecycleObserver(previousState: MutableState): LifecycleEventObserver =
- LifecycleEventObserver { _, event ->
- event.targetState
- when (event) {
- Lifecycle.Event.ON_CREATE -> {
- // Skip calling mapView.onCreate if the lifecycle did not go through onDestroy - in
- // this case the GoogleMap composable also doesn't leave the composition. So,
- // recreating the map does not restore state properly which must be avoided.
- if (previousState.value != Lifecycle.Event.ON_STOP) {
- this.onCreate(Bundle())
- }
- }
+ MapClickListenerUpdater()
- Lifecycle.Event.ON_START -> this.onStart()
- Lifecycle.Event.ON_RESUME -> this.onResume()
- Lifecycle.Event.ON_PAUSE -> this.onPause()
- Lifecycle.Event.ON_STOP -> this.onStop()
- Lifecycle.Event.ON_DESTROY -> {
- //handled in onDispose
+ CompositionLocalProvider(
+ LocalCameraPositionState provides mapUpdaterState.cameraPositionState,
+ content
+ )
}
-
- else -> throw IllegalStateException()
+ awaitCancellation()
+ } finally {
+ composition.dispose()
}
- previousState.value = event
}
+}
-private fun MapView.componentCallbacks(): ComponentCallbacks =
- object : ComponentCallbacks {
- override fun onConfigurationChanged(config: Configuration) {}
+@Stable
+internal class MapUpdaterState(
+ mergeDescendants: Boolean,
+ contentDescription: String?,
+ cameraPositionState: CameraPositionState,
+ contentPadding: PaddingValues,
+ locationSource: LocationSource?,
+ mapProperties: MapProperties,
+ mapUiSettings: MapUiSettings,
+ mapColorScheme: Int?,
+) {
+ var mergeDescendants by mutableStateOf(mergeDescendants)
+ var contentDescription by mutableStateOf(contentDescription)
+ var cameraPositionState by mutableStateOf(cameraPositionState)
+ var contentPadding by mutableStateOf(contentPadding)
+ var locationSource by mutableStateOf(locationSource)
+ var mapProperties by mutableStateOf(mapProperties)
+ var mapUiSettings by mutableStateOf(mapUiSettings)
+ var mapColorScheme by mutableStateOf(mapColorScheme)
+}
- override fun onLowMemory() {
- this@componentCallbacks.onLowMemory()
- }
- }
+/** Used to store things in the tag which must be retrievable across recompositions */
+private data class MapTagData(
+ val componentCallbacks: ComponentCallbacks,
+ val lifecycleObserver: MapLifecycleEventObserver
+)
+
+private val MapView.tagData: MapTagData
+ get() = tag as MapTagData
public typealias GoogleMapFactory = @Composable () -> Unit
@@ -276,6 +306,68 @@ public fun googleMapFactory(
}
}
+private class MapLifecycleEventObserver(private val mapView: MapView) : LifecycleEventObserver {
+ private var currentLifecycleState: Lifecycle.State = Lifecycle.State.INITIALIZED
+
+ override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+ when (event) {
+ // [mapView.onDestroy] is only invoked from AndroidView->onRelease.
+ Lifecycle.Event.ON_DESTROY -> moveToBaseState()
+ else -> moveToLifecycleState(event.targetState)
+ }
+ }
+
+ /**
+ * Move down to [Lifecycle.State.CREATED] but only if [currentLifecycleState] is actually above that.
+ * It's theoretically possible that [currentLifecycleState] is still in [Lifecycle.State.INITIALIZED] state.
+ * */
+ fun moveToBaseState() {
+ if (currentLifecycleState > Lifecycle.State.CREATED) {
+ moveToLifecycleState(Lifecycle.State.CREATED)
+ }
+ }
+
+ fun moveToDestroyedState() {
+ if (currentLifecycleState > Lifecycle.State.INITIALIZED) {
+ moveToLifecycleState(Lifecycle.State.DESTROYED)
+ }
+ }
+
+ private fun moveToLifecycleState(targetState: Lifecycle.State) {
+ while (currentLifecycleState != targetState) {
+ when {
+ currentLifecycleState < targetState -> moveUp()
+ currentLifecycleState > targetState -> moveDown()
+ }
+ }
+ }
+
+ private fun moveDown() {
+ val event = Lifecycle.Event.downFrom(currentLifecycleState)
+ ?: error("no event down from $currentLifecycleState")
+ invokeEvent(event)
+ }
+
+ private fun moveUp() {
+ val event = Lifecycle.Event.upFrom(currentLifecycleState)
+ ?: error("no event up from $currentLifecycleState")
+ invokeEvent(event)
+ }
+
+ private fun invokeEvent(event: Lifecycle.Event) {
+ when (event) {
+ Lifecycle.Event.ON_CREATE -> mapView.onCreate(Bundle())
+ Lifecycle.Event.ON_START -> mapView.onStart()
+ Lifecycle.Event.ON_RESUME -> mapView.onResume()
+ Lifecycle.Event.ON_PAUSE -> mapView.onPause()
+ Lifecycle.Event.ON_STOP -> mapView.onStop()
+ Lifecycle.Event.ON_DESTROY -> mapView.onDestroy()
+ else -> error("Unsupported lifecycle event: $event")
+ }
+ currentLifecycleState = event.targetState
+ }
+}
+
/**
* Enum representing a 1-1 mapping to [com.google.android.gms.maps.model.MapColorScheme].
*
@@ -287,4 +379,4 @@ public enum class ComposeMapColorScheme(public val value: Int) {
LIGHT(MapColorScheme.LIGHT),
DARK(MapColorScheme.DARK),
FOLLOW_SYSTEM(MapColorScheme.FOLLOW_SYSTEM);
-}
\ No newline at end of file
+}
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/MapUpdater.kt b/maps-compose/src/main/java/com/google/maps/android/compose/MapUpdater.kt
index 44a0b5df1..d30298434 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/MapUpdater.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/MapUpdater.kt
@@ -25,8 +25,6 @@ import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import com.google.android.gms.maps.GoogleMap
-import com.google.android.gms.maps.LocationSource
-import com.google.android.gms.maps.model.MapColorScheme
internal class MapPropertiesNode(
val map: GoogleMap,
@@ -97,16 +95,7 @@ public val DefaultMapContentPadding: PaddingValues = PaddingValues()
@SuppressLint("MissingPermission")
@Suppress("NOTHING_TO_INLINE")
@Composable
-internal inline fun MapUpdater(
- mergeDescendants: Boolean = false,
- contentDescription: String?,
- cameraPositionState: CameraPositionState,
- contentPadding: PaddingValues = DefaultMapContentPadding,
- locationSource: LocationSource?,
- mapProperties: MapProperties,
- mapUiSettings: MapUiSettings,
- colorMapScheme: Int?,
-) {
+internal inline fun MapUpdater(mapUpdaterState: MapUpdaterState) = with(mapUpdaterState) {
val map = (currentComposer.applier as MapApplier).map
val mapView = (currentComposer.applier as MapApplier).mapView
if (mergeDescendants) {
@@ -141,7 +130,7 @@ internal inline fun MapUpdater(
set(mapProperties.mapType) { map.mapType = it.value }
set(mapProperties.maxZoomPreference) { map.setMaxZoomPreference(it) }
set(mapProperties.minZoomPreference) { map.setMinZoomPreference(it) }
- set(colorMapScheme) {
+ set(mapColorScheme) {
if (it != null) {
map.mapColorScheme = 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 e73c7d53f..98227a241 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
@@ -38,9 +38,9 @@ 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.StreetViewPanoramaOrientation
-import com.google.maps.android.compose.disposingComposition
import com.google.maps.android.ktx.MapsExperimentalFeature
import com.google.maps.android.ktx.awaitStreetViewPanorama
+import kotlinx.coroutines.awaitCancellation
/**
* A composable for displaying a Street View for a given location. A location might not be available for a given
@@ -131,6 +131,15 @@ private fun StreetViewLifecycle(streetView: StreetViewPanoramaView) {
}
}
+private suspend inline fun disposingComposition(factory: () -> Composition) {
+ val composition = factory()
+ try {
+ awaitCancellation()
+ } finally {
+ composition.dispose()
+ }
+}
+
private suspend inline fun StreetViewPanoramaView.newComposition(
parent: CompositionContext,
noinline content: @Composable () -> Unit