fix: address top Crashlytics crashes and non-fatals for build 29320984 (#5684)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
James Rich
2026-05-31 07:16:10 -07:00
committed by GitHub
parent bc9f163708
commit cca7c274b9
3 changed files with 43 additions and 3 deletions

View File

@@ -18,9 +18,12 @@ 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.setViewTreeLifecycleOwner
import androidx.savedstate.compose.LocalSavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
@@ -45,6 +48,7 @@ fun NodeClusterMarkers(
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
@@ -66,6 +70,10 @@ fun NodeClusterMarkers(
onDispose {}
}
// 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,

View File

@@ -17,10 +17,18 @@
package org.meshtastic.app.node.component
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.defaultMinSize
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.compose.ui.unit.dp
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.compose.LocalSavedStateRegistryOwner
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
@@ -46,6 +54,23 @@ fun InlineMap(node: Node, modifier: Modifier = Modifier) {
true -> ComposeMapColorScheme.DARK
else -> ComposeMapColorScheme.LIGHT
}
// Workaround for maps-compose issue where MarkerComposable's internal ComposeView
// cannot find ViewTreeLifecycleOwner, causing crash on bitmap rendering.
val view = LocalView.current
val lifecycleOwner = LocalLifecycleOwner.current
val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current
DisposableEffect(lifecycleOwner, savedStateRegistryOwner) {
val root = view.rootView
root.setViewTreeLifecycleOwner(lifecycleOwner)
root.setViewTreeSavedStateRegistryOwner(savedStateRegistryOwner)
if (view !== root) {
view.setViewTreeLifecycleOwner(lifecycleOwner)
view.setViewTreeSavedStateRegistryOwner(savedStateRegistryOwner)
}
onDispose {}
}
key(node.num) {
val location = LatLng(node.latitude, node.longitude)
val cameraState = rememberCameraPositionState {
@@ -79,7 +104,9 @@ fun InlineMap(node: Node, modifier: Modifier = Modifier) {
strokeWidth = 2f,
)
}
MarkerComposable(state = rememberUpdatedMarkerState(position = latLng)) { NodeChip(node = node) }
MarkerComposable(state = rememberUpdatedMarkerState(position = latLng)) {
NodeChip(node = node, modifier = Modifier.defaultMinSize(minWidth = 64.dp, minHeight = 28.dp))
}
}
}
}

View File

@@ -23,6 +23,10 @@ import com.juul.kable.logs.LogEngine
* Bridges Kable's internal logging to [Kermit][Logger] so BLE lifecycle events (connect, disconnect, subscribe, GATT
* operations) appear in the standard app logs rather than going to [System.out] via Kable's default
* [com.juul.kable.logs.SystemLogEngine].
*
* Kable logs connection failures and disconnections at error level, but these are expected BLE operational events — not
* application bugs. We downgrade error/assert to warn so these don't trigger non-fatal exception recording in
* Crashlytics (which records any Kermit Error-level log with a throwable as a non-fatal).
*/
internal object KermitLogEngine : LogEngine {
override fun verbose(throwable: Throwable?, tag: String, message: String) {
@@ -42,10 +46,11 @@ internal object KermitLogEngine : LogEngine {
}
override fun error(throwable: Throwable?, tag: String, message: String) {
Logger.e(throwable) { "[$tag] $message" }
// Downgrade: Kable "errors" are operational (failed connect, disconnect requested) not app bugs.
Logger.w(throwable) { "[$tag] $message" }
}
override fun assert(throwable: Throwable?, tag: String, message: String) {
Logger.e(throwable) { "[$tag] $message" }
Logger.w(throwable) { "[$tag] $message" }
}
}