What's the proper way to handle exceptions thrown ...
# compose-desktop
s
What's the proper way to handle exceptions thrown from composable functions? An unchecked java exception thrown from a composable function has frozen the UI and there is no way to recover from it gracefully. So in this case I want to catch (Something like
Thread.UncaughtExceptionHandler
) and continue the recomposition or is this some kind of anti-pattern in compose? 
Copy code
Exception in thread "AWT-EventQueue-0 @coroutine#2" java.lang.IndexOutOfBoundsException: Index 5 out of bounds for length 5
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
	at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:248)
	at java.base/java.util.Objects.checkIndex(Objects.java:359)
	at java.base/java.util.ArrayList.get(ArrayList.java:427)
	at androidx.compose.material.TabRowKt$TabRow$1.invoke(TabRow.kt:117)
	at androidx.compose.material.TabRowKt$TabRow$1.invoke(TabRow.kt)
t
I think you should try to convert this to states. E.g. when you are loading s.th. than this loading composable will return a state. Which can be Loading, Sucess and Error. Than you can show different UI depending on the state.
j
Yes, Timo is correct. At this time, you do not want to have your composable functions be throwing exceptions. Instead, catch the exception directly by wrapping your non-composable function calls in try-catch blocks. When an exception is caught, set some state in your application (eg. set an error message you want to display to the user) and then the app will see the data state change and will recompose, and your composables can read from that variable to see what message (if any) to display to the user.
❤️ 2
t
I use following code (Mainly for image loading but can be used for every thing)
Copy code
sealed class LoadingState<out T: Any> {
    object Start: LoadingState<Nothing>()
    object Loading: LoadingState<Nothing>()
    class Error(val error: Exception): LoadingState<Nothing>()
    class Success<T: Any>(val data: T): LoadingState<T>()
}

@Composable
fun <T: Any> loadingStateFor(vararg inputs: Any?, initBlock: () -> LoadingState<T> = { LoadingState.Start },
                             loadingBlock: suspend CoroutineScope.() -> T): LoadingState<T> {
    var state by remember(*inputs) { mutableStateOf(initBlock()) }
    if (state !is LoadingState.Success) {
        LaunchedEffect(*inputs) {
            val loadingSpinnerDelay = async {
                delay(500)
                state = LoadingState.Loading
            }
            state = try {
                LoadingState.Success(loadingBlock())
            } catch (err: Exception) {
                LoadingState.Error(err)
            } finally {
                loadingSpinnerDelay.cancelAndJoin()
            }
        }
    }
    return state
}
Than you can use it in your composables like that:
Copy code
val imageState = loadingStateFor(image) {
                loadImageSizeFileCached(image.file, width)
            }
            Box(Modifier.fillMaxSize()) {
                InitializedCrossfade(imageState) { state ->
                    when (state) {
                        is LoadingState.Loading -> LoadingBox()
                        is LoadingState.Success -> Image(
                            modifier = Modifier.fillMaxSize(),
                            bitmap = state.data,
                            contentScale = ContentScale.Fit
                        )
                        is LoadingState.Error -> ErrorBox(
                            message = "Error: ${state.error.message}",
                            onRetry = { retryCounter++ })
                    }
                }
            }
s
@jim @Timo Drick thanks!
332 Views