badoo / Decompose

Kotlin Multiplatform lifecycle-aware business logic components (aka BLoCs) with routing functionality and pluggable UI (Jetpack Compose, SwiftUI, JS React, etc.), inspired by Badoos RIBs fork of the Uber RIBs framework
https://arkivanov.github.io/Decompose
Apache License 2.0
814 stars 40 forks source link

Suspend function usage in ViewModels #112

Closed afaucogney closed 3 years ago

afaucogney commented 3 years ago

I have setup a viewmodel in common kmm module, binded with decompose instance keeper, that run suspend function in the init phase of the class instantiation.

I does work fine in Android

But in iOS I get such error at runtime, when I instantiate the ViewModel and I define @ObservedObject from ObserveValue.

struct RootView: View {
    // DEPENDENCY
    private let viewModel: PocFeatureViewModel

    // STATE
    @State var brightness: Double = 5

    @ObservedObject private var viewState: ObservableValue<LightViewState>

    @ObservedObject private var status: ObservableValue<NSString>

    // LIFE CYCLE
    init(_ viewModel: PocFeatureViewModel) {
        self.viewModel = viewModel
        self.viewState = ObservableValue(viewModel.viewState)
        self.status = ObservableValue(viewModel.status)
    }

    var body: some View {
   // ...
    }
}

Here is the trace. Any idea ?

Function doesn't have or inherit @Throws annotation and thus exception isn't propagated from Kotlin to Objective-C/Swift as NSError. It is considered unexpected and unhandled instead. Program will be terminated. Uncaught Kotlin exception: kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen com.arkivanov.decompose.value.MutableValueImpl@1ad5008 Uncaught Kotlin exception: kotlinx.coroutines.CoroutinesInternalError: Fatal exception in coroutines machinery for DispatchedContinuation[WorkerCoroutineDispatcherImpl@1419118, Continuation @ $start$lambda-0COROUTINE$0]. Please read KDoc to 'handleFatalException' method and report this incident to maintainers

arkivanov commented 3 years ago

Hi, the issue seems with the view module, looks like it captures Value in a callback and it gets frozen due to thread switching.

arkivanov commented 3 years ago

I can try to make it freezable though. But at the moment Value can not be frozen.

afaucogney commented 3 years ago

That's definitively something like that. So how can I handle it ?
I'm young to kmm dev, so how can I handle thread switch just before updating the data ?

With Android live data, you can set value by livedata.value = myvalue (in uiThread) or livedata.postValue(myvalue) in any thread. A simple solution would be perfect !

arkivanov commented 3 years ago

I'm not sure what would be a proper way with coroutines. Reaktive has built-in threadLocal operator for this, MVIKotlin also handles this under the hood. But I don't remember anything like this for Coroutines. Maybe ask in #kotlin-native Kotlinlang Slack channel? This looks like a general Kotlin/Native + Coroutines question. From my side I will think about making Value freezable. But at the moment I'm not sure if it is a good way, because all subscribers will be also frozen.

arkivanov commented 3 years ago

Closing for now