https://kotlinlang.org logo
#mvikotlin
Title
# mvikotlin
t

trashcoder

08/22/2022, 6:37 PM
hello, i need some help with setting the
main thread
for
jvm
and
dispaching
from it. (detailed description in slack thread)
1
i have working multiplatform implementations with mvikotlin and decompse for
android
and
jvm
(windows). i used to have some "not on main thread" warnings on
android
which i could fix with observing (and
dispatching
) on mainscheduler. but i never got any warnings for
jvm
. now i noticed this message in the
jvm
log for the first time:
Copy code
[MVIKotlin]: Main thread id is undefined, main thread assert is disabled
when i set the main thread in the
jvm
main function using
setMainThreadId(Thread.currentThread().id)
it breaks my implementation (which works fine on
android
). i cannot
dispatch
from within
scope.launch{}
anymore. but
disatching
before launching works. works without setting the main thread on
jvm
and `android`:
Copy code
private inner class ExecutorImpl : CoroutineExecutor<Intent, Unit, State, Msg, Nothing>() {
    override fun executeAction(action: Unit, getState: () -> State) {
        scope.launch {
            dispatch(Msg.Booting)
works on
jvm
when i set the main thread:
Copy code
private inner class ExecutorImpl : CoroutineExecutor<Intent, Unit, State, Msg, Nothing>() {
    override fun executeAction(action: Unit, getState: () -> State) {
        dispatch(Msg.Booting)
        scope.launch {
i do not change the scope's context btw. it is still the default (main).
a

Arkadii Ivanov

08/22/2022, 6:40 PM
Hello. What kind of main dispatcher you are using on JVM? Is it Swing?
t

trashcoder

08/22/2022, 6:50 PM
Erm... I don't know 🙈 I use Jetbrains Compose for UI... How can I determine my main dispatcher?
a

Arkadii Ivanov

08/22/2022, 6:53 PM
In this case it should be Swing - https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-swing/ How do you set the main thread id on JVM? Where do you call the
setMainThreadId
method?
t

trashcoder

08/22/2022, 7:03 PM
i set it directly at the beginning of the main function:
Copy code
fun main() {
    setMainThreadId(Thread.currentThread().id)

    val lifecycle = LifecycleRegistry()
    val root = root(DefaultComponentContext(lifecycle = lifecycle))

    application {
        val windowState = rememberWindowState()
        val icon = painterResource("ic_launcher-desktop.png")

        LifecycleController(lifecycle, windowState)

        Tray(
            icon = icon,
            menu = {
                Item("Beenden", onClick = ::exitApplication)
            }
        )

        Window(
            onCloseRequest = ::exitApplication,
            state = windowState,
            title = "Trashboard",
            icon = icon
        ) {
            App(root)
        }
    }
}
a

Arkadii Ivanov

08/22/2022, 7:07 PM
Right, this is a mistake. The
main
function runs not on the UI thread. You can try the following code:
Copy code
SwingUtilities.invokeAndWait {
  setMainThreadId(Thread.currentThread().id)
}
a

Arkadii Ivanov

08/22/2022, 7:33 PM
I would say yes, if you want the check to be enabled. Otherwise it may crash. You can use that code as an example. Let me know, if there are any issues!
t

trashcoder

08/22/2022, 7:45 PM
this works for me:
Copy code
fun main() {
    val lifecycle = LifecycleRegistry()

    lateinit var root: Root
    SwingUtilities.invokeAndWait {
        setMainThreadId(Thread.currentThread().id)

        root = root(DefaultComponentContext(lifecycle = lifecycle))
    }

    application {
        val windowState = rememberWindowState()
        val icon = painterResource("ic_launcher-desktop.png")

        LifecycleController(lifecycle, windowState)

        Tray(
            icon = icon,
            menu = {
                Item("Beenden", onClick = ::exitApplication)
            }
        )

        Window(
            onCloseRequest = ::exitApplication,
            state = windowState,
            title = "Trashboard",
            icon = icon
        ) {
            App(root)
        }
    }
}
thanks a lot! 🙂
hello again, i ran into 2 follow up issues after setting the main thread for jvm. 1. [FYI/SOLVED] i observe some server sent and push events with observables. i used to
observeOn(mainScheduler)
and then call my callback/event in subcribe which is implemented in the
store
, so the store can
dispatch
the new state directly. this does not work anymore because the (swing awt) main thread is not the same as the
mainScheduler
. i solved this by removing
observeOn(mainScheduler)
and wrapping the
dispatch
in the
store
with
scope.launch{}
. before:
Copy code
observable<String> { emitter ->
    ...
}.observeOn(mainScheduler).subscribe {
    onServerSentEvent(it)
}

onServerSentEvent = sse -> {
    dispatch(Msg.SSE(sse))
}
after:
Copy code
observable<String> { emitter ->
    ...
}.subscribe {
    onServerSentEvent(it)
}

onServerSentEvent = sse -> {
    scope.launch {
        dispatch(Msg.SSE(sse))
    }
}
2. I use an
mqtt
client to
subscribe
to a server (my roomba). the client has callbacks for listening to subscription messages. however, when i set the main thread, i can still connect successfully and subscribe but i never receive a subscription message in the subscription listener. so something seems to be blocking this...? i tried suspending at several locations with several contexts/dispatchers but it never worked. if i remove
setMainThreadId
, i receive the messages again. any idea/hint what i could do about it? (other than not setting the main thread of course 😉)
btw: both problems only occured on jvm after setting the main thread. android still works fine.
a

Arkadii Ivanov

08/23/2022, 7:59 PM
Re issue 1: you should definitely use observeOn(mainScheduler), and it should switch to the right thread. Most likely your mainScheduler implementation uses another thread for dispatching. If you are using the default one, then it's just a random single background thread. You can either implement the mainScheduler using Swing UI thread, or just wrap the coroutines main dispatcher as I did here - https://github.com/arkivanov/MVIKotlin/blob/8d06462eff0225a99111633cc99f024f8de2b52f/mvikotlin-timetravel-client/app-desktop/src/jvmMain/kotlin/com/arkivanov/mvikotlin/timetravel/client/desktop/Main.kt#L38
Re issue 2: unfortunately I'm not familiar with mqtt. This sounds like you are not receiving messages if you are subscribing on the UI thread. I would refer to a documentation for mqtt or something.
Btw, you can omit observeOn(mainScheduler) if you are sure that the upstream already emits on the main thread. This will avoid unnecessary re-dispatches. But you definitely have to use mainScheduler for switching from background threads.
t

trashcoder

08/23/2022, 8:17 PM
issue 1 is solved in a better way now, thanks! 🙂
FYI: issue 2 is also a "not on main thread" error on dispatching the mqtt client's connection result. unfortunately, the client catches it (it happens in the clients callback) and so it was never seen in console... 🙈
a

Arkadii Ivanov

08/23/2022, 10:46 PM
So, did you solve it?
t

trashcoder

08/24/2022, 6:29 PM
yes. i don't use an obervable there, so i just wrapped it with `MainScope().launch{}`:
Copy code
MainScope().launch {
    onConnectionResult(true)
}

MainScope().launch {
    onMessage(robotId, robotUpdateMessage)
}
186 Views