mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-06-01 22:19:18 +02:00
fix(map): remove manual ViewTree lifecycle owner workarounds (#5704)
This commit is contained in:
@@ -17,18 +17,7 @@
|
||||
package org.meshtastic.app.map.component
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.lifecycle.compose.currentStateAsState
|
||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||
import androidx.lifecycle.setViewTreeLifecycleOwner
|
||||
import androidx.savedstate.compose.LocalSavedStateRegistryOwner
|
||||
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
|
||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||
import com.google.maps.android.clustering.Cluster
|
||||
import com.google.maps.android.clustering.view.DefaultClusterRenderer
|
||||
import com.google.maps.android.compose.Circle
|
||||
@@ -47,50 +36,6 @@ fun NodeClusterMarkers(
|
||||
navigateToNodeDetails: (Int) -> Unit,
|
||||
onClusterClick: (Cluster<NodeClusterItem>) -> Boolean,
|
||||
) {
|
||||
val view = LocalView.current
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current
|
||||
val lifecycleState by lifecycleOwner.lifecycle.currentStateAsState()
|
||||
|
||||
// Workaround for https://github.com/googlemaps/android-maps-compose/issues/858
|
||||
// and https://github.com/googlemaps/android-maps-compose/issues/875
|
||||
// The maps clustering library creates an internal ComposeView to snapshot markers.
|
||||
// If that view is not attached to the hierarchy (which it often isn't during rendering),
|
||||
// it fails to find the Lifecycle and SavedState owners. We propagate them to the root view
|
||||
// so the internal snapshot view can find them when walking up the tree.
|
||||
// DisposableEffect runs at composition time (not post-composition like SideEffect),
|
||||
// ensuring owners are set before the Clustering composable triggers marker rendering.
|
||||
// Capture and restore the previous owners on dispose. The owners here are NavEntry-scoped
|
||||
// (transient) lifecycles; leaving one attached to the activity root view after this screen is
|
||||
// destroyed makes subsequently opened Popups/DropdownMenus inherit a DESTROYED lifecycle and
|
||||
// render at 0x0 (invisible). See the node-list popup regression and InlineMap.
|
||||
DisposableEffect(lifecycleOwner, savedStateRegistryOwner) {
|
||||
val root = view.rootView
|
||||
val prevRootLifecycleOwner = root.findViewTreeLifecycleOwner()
|
||||
val prevRootSavedStateRegistryOwner = root.findViewTreeSavedStateRegistryOwner()
|
||||
root.setViewTreeLifecycleOwner(lifecycleOwner)
|
||||
root.setViewTreeSavedStateRegistryOwner(savedStateRegistryOwner)
|
||||
// Also set on the view itself in case the internal renderer walks from a child
|
||||
val prevViewLifecycleOwner = view.findViewTreeLifecycleOwner()
|
||||
val prevViewSavedStateRegistryOwner = view.findViewTreeSavedStateRegistryOwner()
|
||||
if (view !== root) {
|
||||
view.setViewTreeLifecycleOwner(lifecycleOwner)
|
||||
view.setViewTreeSavedStateRegistryOwner(savedStateRegistryOwner)
|
||||
}
|
||||
onDispose {
|
||||
root.setViewTreeLifecycleOwner(prevRootLifecycleOwner)
|
||||
root.setViewTreeSavedStateRegistryOwner(prevRootSavedStateRegistryOwner)
|
||||
if (view !== root) {
|
||||
view.setViewTreeLifecycleOwner(prevViewLifecycleOwner)
|
||||
view.setViewTreeSavedStateRegistryOwner(prevViewSavedStateRegistryOwner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Guard against the cluster renderer's async Handler trying to render markers
|
||||
// after the lifecycle has stopped — the internal ComposeView requires an active lifecycle.
|
||||
if (!lifecycleState.isAtLeast(Lifecycle.State.STARTED)) return
|
||||
|
||||
Clustering(
|
||||
items = nodeClusterItems,
|
||||
onClusterClick = onClusterClick,
|
||||
|
||||
@@ -18,17 +18,9 @@ package org.meshtastic.app.node.component
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||
import androidx.lifecycle.setViewTreeLifecycleOwner
|
||||
import androidx.savedstate.compose.LocalSavedStateRegistryOwner
|
||||
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
|
||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||
import com.google.android.gms.maps.model.CameraPosition
|
||||
import com.google.android.gms.maps.model.LatLng
|
||||
import com.google.maps.android.compose.Circle
|
||||
@@ -52,31 +44,6 @@ fun InlineMap(node: Node, modifier: Modifier = Modifier) {
|
||||
true -> ComposeMapColorScheme.DARK
|
||||
else -> ComposeMapColorScheme.LIGHT
|
||||
}
|
||||
|
||||
// Defensive workaround: propagate ViewTreeLifecycleOwner to the root view so that
|
||||
// any internal maps-compose ComposeView (e.g., info windows) can find the lifecycle
|
||||
// when walking up the view tree.
|
||||
//
|
||||
// IMPORTANT: capture and restore the previous owners on dispose. This InlineMap is hosted inside the
|
||||
// node-detail NavEntry, whose LocalLifecycleOwner is a transient, entry-scoped lifecycle. Leaving it
|
||||
// attached to the activity root view after the entry is destroyed (e.g. navigating back to the node
|
||||
// list) would make subsequently opened Popups/DropdownMenus inherit a DESTROYED lifecycle and
|
||||
// render at 0x0 (invisible). See the node-list popup regression.
|
||||
val view = LocalView.current
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current
|
||||
DisposableEffect(lifecycleOwner, savedStateRegistryOwner) {
|
||||
val root = view.rootView
|
||||
val prevRootLifecycleOwner = root.findViewTreeLifecycleOwner()
|
||||
val prevRootSavedStateRegistryOwner = root.findViewTreeSavedStateRegistryOwner()
|
||||
root.setViewTreeLifecycleOwner(lifecycleOwner)
|
||||
root.setViewTreeSavedStateRegistryOwner(savedStateRegistryOwner)
|
||||
onDispose {
|
||||
root.setViewTreeLifecycleOwner(prevRootLifecycleOwner)
|
||||
root.setViewTreeSavedStateRegistryOwner(prevRootSavedStateRegistryOwner)
|
||||
}
|
||||
}
|
||||
|
||||
key(node.num) {
|
||||
val location = LatLng(node.latitude, node.longitude)
|
||||
val cameraState = rememberCameraPositionState {
|
||||
|
||||
Reference in New Issue
Block a user