mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-06-01 10:39:00 -04:00
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:
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user