EARHORNMUSIC UPnP · Android TV · Phones · Android Auto · GNU/Linux
Release 0.5.0

SafeLaunch.kt

42 lines · 1.9 KB

Changed in: 0.5.0

1package dev.stave.ui
2
3import androidx.lifecycle.ViewModel
4import androidx.lifecycle.viewModelScope
5import kotlinx.coroutines.CoroutineExceptionHandler
6import kotlinx.coroutines.CoroutineScope
7import kotlinx.coroutines.Job
8import kotlinx.coroutines.launch
9
10/**
11 * Coroutine exception handler shared by every ViewModel.launch site.
12 *
13 * Without this, uncaught exceptions from viewModelScope.launch bubble up to the JVM
14 * default handler, which Compose Desktop / Swing surfaces as an opaque "Error"
15 * dialog showing only the exception message — e.g. `index: 57, size: 57` after a
16 * right-click → Play Next, with no stack trace and no way to recover gracefully.
17 *
18 * Logging here means the same exception becomes a stderr stack trace
19 * (recoverable from `./gradlew :desktop:run` output or a shell-launched packaged
20 * install) and the user keeps playing music. The app remains responsive.
21 *
22 * Note: This swallows the exception by *handling* it — viewModelScope's
23 * SupervisorJob doesn't cancel siblings, so other launches continue normally.
24 */
25private val viewModelExceptionHandler = CoroutineExceptionHandler { _, throwable ->
26 System.err.println(
27 "[Earhorn] ViewModel coroutine threw (${throwable.javaClass.simpleName}):"
28 )
29 throwable.printStackTrace(System.err)
30}
31
32/**
33 * Like viewModelScope.launch, but installs [viewModelExceptionHandler] so an
34 * unexpected throw becomes a logged stack trace rather than an error dialog.
35 *
36 * Use for any coroutine triggered by a user action (right-click handlers,
37 * button clicks, menu items, drag callbacks, etc). Long-running collection
38 * jobs (state flows, lifecycle observers) can also use this; the handler
39 * only fires on actually-uncaught exceptions.
40 */
41fun ViewModel.safeLaunch(block: suspend CoroutineScope.() -> Unit): Job =
42 viewModelScope.launch(viewModelExceptionHandler, block = block)