-
Notifications
You must be signed in to change notification settings - Fork 1
/
ErrorBoundary.kt
81 lines (71 loc) · 2.38 KB
/
ErrorBoundary.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// Copyright 2024 Soil Contributors
// SPDX-License-Identifier: Apache-2.0
package soil.query.compose.runtime
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
@Composable
fun ErrorBoundary(
modifier: Modifier = Modifier,
fallback: (@Composable BoxScope.(ctx: ErrorBoundaryContext) -> Unit)? = null,
onError: ((err: Throwable) -> Unit)? = null,
onReset: (() -> Unit)? = null,
state: ErrorBoundaryState = remember { ErrorBoundaryState() },
content: @Composable () -> Unit
) {
val currentError by remember(state) { derivedStateOf { state.error } }
val onErrorCallback by rememberUpdatedState(newValue = onError)
val onResetCallback by rememberUpdatedState(newValue = onReset)
Box(modifier) {
CompositionLocalProvider(
LocalCatchThrowHost provides state
) {
ContentVisibility(
hidden = currentError != null && fallback != null,
content = content
)
}
val err = currentError
if (err != null) {
val ctx = remember(err, onResetCallback) {
ErrorBoundaryContext(err, onResetCallback)
}
if (fallback != null) {
fallback(ctx)
}
LaunchedEffect(err) {
onErrorCallback?.invoke(err)
}
}
}
}
@Stable
class ErrorBoundaryContext(
val err: Throwable,
val reset: (() -> Unit)?
)
@Stable
class ErrorBoundaryState : CatchThrowHost {
private val hostMap = mutableStateMapOf<Any, Throwable>()
val error: Throwable?
get() = hostMap.values.firstOrNull()
override val keys: Set<Any> get() = hostMap.keys
override fun get(key: Any): Throwable? {
return hostMap[key]
}
override fun set(key: Any, error: Throwable) {
hostMap[key] = error
}
override fun remove(key: Any) {
hostMap.remove(key)
}
}