mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-06-02 06:24:16 +02:00
fix(node): restore view-tree owners on map dispose so node-list popups aren't invisible (#5699)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+18
-1
@@ -24,8 +24,10 @@ 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
|
||||
@@ -58,16 +60,31 @@ fun NodeClusterMarkers(
|
||||
// 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 {}
|
||||
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
|
||||
|
||||
@@ -26,8 +26,10 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.unit.dp
|
||||
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
|
||||
@@ -55,20 +57,28 @@ fun InlineMap(node: Node, modifier: Modifier = Modifier) {
|
||||
else -> ComposeMapColorScheme.LIGHT
|
||||
}
|
||||
|
||||
// Workaround for maps-compose issue where MarkerComposable's internal ComposeView
|
||||
// cannot find ViewTreeLifecycleOwner, causing crash on bitmap rendering.
|
||||
// Workaround for a maps-compose issue where MarkerComposable's internal ComposeView cannot find a
|
||||
// ViewTreeLifecycleOwner, causing a crash on bitmap rendering. We attach the current owners to the
|
||||
// window root view for the lifetime of the map.
|
||||
//
|
||||
// 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 every subsequently opened Popup/DropdownMenu 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)
|
||||
if (view !== root) {
|
||||
view.setViewTreeLifecycleOwner(lifecycleOwner)
|
||||
view.setViewTreeSavedStateRegistryOwner(savedStateRegistryOwner)
|
||||
onDispose {
|
||||
root.setViewTreeLifecycleOwner(prevRootLifecycleOwner)
|
||||
root.setViewTreeSavedStateRegistryOwner(prevRootSavedStateRegistryOwner)
|
||||
}
|
||||
onDispose {}
|
||||
}
|
||||
|
||||
key(node.num) {
|
||||
|
||||
Reference in New Issue
Block a user