the nice thing about java is that you can catch pr...
# compose-desktop
z
the nice thing about java is that you can catch pretty much any issue with try/catch. with compose its very annoying when you get a compose error the app is frozen and there is no way to recover. is there a try/catch for runtime compose errors? or any way to recover the frozen app programatically?
plus1 2
i
Copy code
singleWindowApplication {
    @OptIn(ExperimentalComposeUiApi::class)
    window.exceptionHandler = WindowExceptionHandler { e -> println("Exception in Compose: $e") }
}
z
oh wow. does that work? i will try now
sadly that does not work. the compose exception and freeze still happen. do you have a working example that shows this actually works?
i
Catching exception doesn't mean that the program is automatically in a good state and might continue run. What are you trying to catch? Is there minimal sample? What's the "expected" behaviour for you?
z
i have fixed the particular issue. but from time to time there can be another issue. it is in my view the worst for any user if the app is frozen and he cant do anything anymore. if possible i would like to avoid that in the future. i cant possibly know or fix all issues in my app that may happen in all edge cases. at some point there will be another issue
s
Indeed, you should definitely avoid throwing exceptions in composables. I have a redux pattern like in the kmm-production-sample and my dispatch method from the store has a catch on throwable for this reason. Composables usually don’t throw exceptions.
Would be nice to see support for try-catch in composables some day, but I guess that’s hard to implement
i
do you have a working example that shows this actually works?
Prepared a sample that recreates a window after exception. It uses a bit different API that I posted above to set this handler early + have some control out of "application" block.
Copy code
fun main() {
    while (true) {
        try {
            windowWithExceptionHandler {
                ComposeContent()
            }
            return
        } catch (exception: Throwable) {
            reportException(exception)
        }
    }
}

@OptIn(ExperimentalComposeUiApi::class)
fun windowWithExceptionHandler(content: @Composable FrameWindowScope.() -> Unit) {
    var lastException: Throwable? = null
    val windowExceptionHandlerFactory = WindowExceptionHandlerFactory { window ->
        WindowExceptionHandler { exception ->
            lastException = exception
            window.dispatchEvent(WindowEvent(window, WindowEvent.WINDOW_CLOSING))
        }
    }
    application(exitProcessOnExit = false) {
        CompositionLocalProvider(
            LocalWindowExceptionHandlerFactory provides windowExceptionHandlerFactory
        ) {
            Window(onCloseRequest = ::exitApplication, content = content)
        }
    }
    lastException?.let { throw it } // Re-throw for regular try/catch block
}

fun reportException(exception: Throwable) {
    val message = exception.message ?: "Unknown error"
    println("Exception in Compose: $message")
}

@Composable
fun ComposeContent() {
    var crash by remember { mutableStateOf(false) }
    Button(onClick = { crash = true }) { Text("Crash") }
    if (crash) {
        Box(Modifier.size(1_000_000.dp)) // Invalid layout
    }
}
This pattern was already discussed here - https://github.com/JetBrains/compose-multiplatform/issues/1764 Recover state for only a part of compose tree is more tricky and doesn't seem so reliable
s
I use that too to show users of Ashampoo Photos a „sorry, we crashed“ message, but this is not the same as the ability to properly handle exceptions in composables. That’s not possible AFAIK
i
Yes, it's about taking care of unhanded ones. There is no more granular control for handling it in composing phase yet. Related issue - https://github.com/JetBrains/compose-multiplatform/issues/2582
👍 1