Crashlytics сообщает о странной ошибке, по-видимому, приложение никогда не вылетало во время разработки. Мне не удалось найти в сети ни одного случая возникновения этой (точной) ошибки. У нас есть Composable, который рисует дугу с некоторыми данными, извлеченными из модели представления. Ни один из данных не является пустым, все имеют значения по умолчанию, поэтому я думаю, что ошибка null не связана с этим. Один зарегистрированный случай этой ошибки произошел, когда пользователь понизил разрешение на местоположение с Все время до Во время использования (даже несмотря на то, что приложение было закрыто этим действием системой).
Вот Composable с использованием Canvas:
Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.compose.ui.graphics.layer.GraphicsLayer.drawForPersistence$ui_graphics_release(androidx.compose.ui.graphics.Canvas)' on a null object reference
at androidx.compose.ui.graphics.layer.LayerManager.persistLayers(LayerManager.android.kt:116)
at androidx.compose.ui.graphics.layer.LayerManager.updateLayerPersistence(LayerManager.android.kt:136)
at androidx.compose.ui.graphics.AndroidGraphicsContext$componentCallback$1$onTrimMemory$1.onPreDraw(AndroidGraphicsContext.android.kt:79)
at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:1204)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:4739)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:3302)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:11362)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1689)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1698)
at android.view.Choreographer.doCallbacks(Choreographer.java:1153)
at android.view.Choreographer.doFrame(Choreographer.java:1079)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1646)
at android.os.Handler.handleCallback(Handler.java:958)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:230)
at android.os.Looper.loop(Looper.java:319)
at android.app.ActivityThread.main(ActivityThread.java:8919)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:578)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)
@Composable
private fun DrawArc(
progress: ArcScoreConfig,
progressColor: Color,
strokeWidth: Dp,
endCircleColor: Color = DesignTokenColor.colorSemanticDatavizIndicator,
circularEnd: Boolean = false,
animate: Boolean = true,
) {
val target: MutableFloatState = rememberSaveable(progress.score) {
mutableFloatStateOf
(0f) }
//animation for the arc, arc is animated fairly simple
//we draw arc using percentages of the float
//so if the the score is 83 we multiply that we rising value
//of our "target" (0-1), at all times arc (and circle) are just relatively
//scaled down (by their angle). As the float value is rising, so is arc's end
//angle/
val animation: Float by animateFloatAsState(
targetValue = target.floatValue,
animationSpec =
tween
(
durationMillis = DiscountArcProgressConfig.animationDuration,
delayMillis = DiscountArcProgressConfig.animationDelay,
easing =
LinearEasing
),
label = "Arc animation"
)
LaunchedEffect(progress.score) {
//trigger animation to start, must be triggered each time score changes
//otherwise, arc won't be animated, only the end result will be drawn
target.floatValue = progress.score.toFloat()
}
Column {
Canvas(
modifier = Modifier.
aspectRatio
(DiscountArcProgressConfig.aspectRatio)
) {
val centerX = size.width / 2
val centerY = size.height
val path =
Path
().
apply
{
arcTo(
rect = Rect(
centerX - centerY,
centerY - centerY,
centerX + centerY,
centerY + centerY
),
startAngleDegrees = DiscountArcProgressConfig.startAngle,
//if animate flag is false, we just draw the arc with progress
//dashboard case would be 100, full half-circle
//if true, we use the animation
sweepAngleDegrees = DiscountArcProgressConfig.sweepAngle(
if (animate)
animation else progress.score.toFloat()
),
forceMoveTo = false
)
}
drawPath(
path = path,
color = progressColor,
style = Stroke(
width = strokeWidth.
toPx
(),
cap = StrokeCap.Round
)
)
if (circularEnd) {
drawCircle(
color = endCircleColor,
radius = strokeWidth.
toPx
() / 2,
center = DiscountArcProgressConfig.circleOffset(
centerX,
centerY,
animation
)
)
}
}
}
}