Git Product home page Git Product logo

kmp-observableviewmodel's People

Contributors

chrisbanes avatar rickclephas avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

kmp-observableviewmodel's Issues

State update is reinitializing the entire screen in SwiftUI

There's an issue when for example in shared part:

init { viewModelScope.coroutineScope.launch { while (true) { delay(2000) _homeUiState.update { it.copy(error = if (it.error == null) "error" else null) } } } }

When in SwiftUI using this view model as: @EnvironmentViewModel private var homeViewModel: HomeViewModel

and if you implement in SwiftUI screen:

init() { print("code 1 - init") }

.onChange(of: homeViewModel.homeUiState.error) { error in if error != nil { showAlert = true } else { showAlert = false } }

The screen will always be reinitialized. You will always get code 1 - init after state change.

JVM support

@rickclephas we chatted a little about this and think you mentioned you might add JVM target at some point and just creating this to track. I can run projects I have that do include JVM target but just get error when syncing.

ViewModel State In BaseViewModel With Generic Type Compiles As `Any`

I'm not sure if this is an issue with Kotlin/Native itself but I have a BaseViewModel with generic type T which is used to construct a stateflow:

abstract class BaseViewModel<T: Any>(initialState: T) : KMMViewModel() {
    protected val scope = viewModelScope.coroutineScope
    private val _state = MutableStateFlow(viewModelScope, initialState)
    @NativeCoroutinesState val state = _state.asStateFlow()

    protected fun setState(newState: T) {
        _state.value = newState
    }
}

All viewmodels in the project subclass this like so:

class CurrenciesViewModel(
    private val getCurrencies: GetCurrencies
) : BaseViewModel<CurrenciesViewModel.State>(State.Idle) {

    init {
        loadCurrencies()
    }

    sealed class State {
        object Idle : State()
        data class Content(
            val currencies: Map<String, List<Currency>>,
        ) : State()
    }
}

When using this in Xcode, viewModel.state in Swift has a type of Any, which means i have to cast it:

viewModel.state as! CurrenciesViewModel.State

Any idea why?
Thanks in advance

XCode preview crash

KMM-ViewModel 1.0.0-ALPHA20
Kotlin Multiplatform 1.9.23

When using KMM-ViewModel, I have a crash in XCode Preview:

== PREVIEW UPDATE ERROR:

    CrashReportError: Fatal Error in ObservableViewModelPublishers.swift
    
    MSCO Cash Register Simulator crashed due to fatalError in ObservableViewModelPublishers.swift at line 26.
    
    ObservableViewModel has been deallocated

Cannot access 'androidx.lifecycle.ViewModel' which is a supertype of 'com.example.account.AccountViewModel'

First of all thanks for all the awesome libs.
I have added this dependency in my shared folder.
api("com.rickclephas.kmm:kmm-viewmodel-core:1.0.0-ALPHA-9") and created this class
class AccountViewModel : KMMViewModel() {}
and I am getting this error

Cannot access 'androidx.lifecycle.ViewModel' which is a supertype of 'com.example.account.AccountViewModel'. Check your module classpath for missing or conflicting dependencies

Am I missing some dependency or some configuration?

Crash while pressing “submit” on a keyboard of a TextField

Issue:

I get a crash while pressing “submit” on keyboard of a TextField, that uses a published var from the viewModel.

“Thread 1: Simultaneous accesses to 0x600002e2b358, but modification requires exclusive access” on ObservableViewModel:25

Setup:

iOS viewModel is extending shared.ViewModel

on the viewModel side, the Published var used as input text binding for Textfield is defined like this:

@Published var username: String = "" {
        didSet {
            isUsernameValid = validateUsername(email: username)

            validatePasswordLogin()
        }
    }

ViewModel var inside the View is annotated with @StateViewModel

struct LoginView: View {
    @StateViewModel var viewModel: LoginViewModel

    var body: some View {
        TextField(text: $viewModel.username, label: {
            Text("test")
        })
    }
}

Proposed fix:

Since simultaneous access could be avoided by doing the writing async, i tried the following and it fixed the crash - no idea if its a worthy solution here.

ObservableViewModel:35 - the function that does the setting of keypath, i wrapped that line inside a DispatchQueue.main.async

     public func set<T>(_ keyPath: WritableKeyPath<ViewModel, T>, to newValue: T) {
        DispatchQueue.main.async {
            self._viewModel[keyPath: keyPath] = newValue
        }
     }

Versions on the iOS side:

pod 'KMMViewModelCore', :git => 'https://github.com/rickclephas/KMM-ViewModel.git', :commit => '047d8c597715d57be2951d121f686c2886e5a39f'
pod 'KMMViewModelCoreObjC', :git => 'https://github.com/rickclephas/KMM-ViewModel.git', :commit => '047d8c597715d57be2951d121f686c2886e5a39f'
pod 'KMMViewModelSwiftUI', :git => 'https://github.com/rickclephas/KMM-ViewModel.git', :commit => '047d8c597715d57be2951d121f686c2886e5a39f'

Crash in collapsible view with child view model (iOS)

I have a KMMViewModel subclass that contains a child view model that's also a KMMViewModel subclass. The child view model is passed to a child view that is collapsible. I can collapse the view fine, but the app crashes when I try to expand it again.

Relevant stack trace excerpt

Thread 0 Crashed::  Dispatch queue: com.apple.main-thread
0  libobjc.A.dylib  0x104d49454 objc_retain + 16
1  iosApp           0x1049d2520 observableViewModel<A>(for:) + 104
2  iosApp           0x1049d1414 ObservedViewModel.init(wrappedValue:) + 68
3  iosApp           0x1049cd000 ChildView.init(viewModel:) + 180 (RootScreen.swift:30)
4  iosApp           0x1049cc914 closure #1 in RootScreen.body.getter + 1004 (RootScreen.swift:19)
5  iosApp           0x1049ccce0 partial apply for closure #1 in RootScreen.body.getter + 16
6  SwiftUI          0x1062294d0 0x105654000 + 12408016
7  iosApp           0x1049cc4bc RootScreen.body.getter + 344 (RootScreen.swift:10)

Kotlin view models:

open class RootViewModel : KMMViewModel() {
    @NativeCoroutinesState
    val childViewModel: MutableStateFlow<ChildViewModel> = MutableStateFlow(viewModelScope, ChildViewModel("Child"))
}

open class ChildViewModel(name: String) : KMMViewModel() {
    @NativeCoroutinesState
    val name: MutableStateFlow<String> = MutableStateFlow(viewModelScope, name)
}

Swift Views

struct RootScreen: View {
    @StateViewModel var viewModel = RootViewModel()
    @State private var isExpanded: Bool = true

    var body: some View {
        HStack {
            Button(
                action: { isExpanded.toggle() },
                label: { Image(systemName: isExpanded ? "chevron.right" : "chevron.left") }
            )

            if (isExpanded) {
                ChildView(viewModel: viewModel.childViewModel)
            }

            Spacer()
        }
        .padding()
    }
}

struct ChildView: View {
    @ObservedViewModel var viewModel: ChildViewModel

    init(viewModel: ChildViewModel) {
        _viewModel = ObservedViewModel(wrappedValue: viewModel)
    }

    var body: some View {
        Text(viewModel.name)
    }
}

Sample project to demonstrate

https://github.com/humblehacker/KMMViewModelCrashExample

steps:

  • Tap the chevron > to collapse the view
  • Tap again to expand 💥

Comments

It looks like when the view is collapsed, the child view model is destroyed. So when it's expanded again it crashes when we try to set the associated object on a dangling pointer.

Is there a better way to model this kind of relationship that might avoid this problem?

Android target includes a BuildConfig class

Just letting you know that the Android includes the generated BuildConfig class. Libraries should avoid exporting these as they can easily clash with in application projects.

You can disable it globally by setting android.defaults.buildfeatures.buildconfig = false in your gradle.properties

Module with the Main dispatcher is missing on desktop JVM

when i try to use any function of the shared viewmodel from the jvm(desktop) target i get this error, even though i added the coroutines as the log suggests i still get the same error

Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android' and ensure it has the same version as 'kotlinx-coroutines-core

Could not find "org.jetbrains.kotlin.native.platform.CoreFoundationBase"

Hi @rickclephas, here is a bit of context about the problem:
*Version: 1.0.0-ALPHA-8
*I've followed the README and for android everything is working OK. However, I added the package to Xcode and when I built the project it thrown an exception. Here is an extract of it:
The following Kotlin source sets were configured but not added to any Kotlin compilation: androidAndroidTestRelease androidTestFixtures androidTestFixturesDebug androidTestFixturesRelease You can add a source set to a target's compilation by connecting it with the compilation's default source set using 'dependsOn'. See https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#connecting-source-sets Task :shared:compileKotlinIosSimulatorArm64 FAILED e: Could not find "org.jetbrains.kotlin.native.platform.CoreFoundationBase" in [/Users/pabloski/projects/ShoppingListClientMobile, /Users/pabloski/.konan/klib, /Users/pabloski/.konan/kotlin-native-prebuilt-macos-aarch64-1.7.20/klib/common, /Users/pabloski/.konan/kotlin-native-prebuilt-macos-aarch64-1.7.20/klib/platform/ios_simulator_arm64]

I then tried adding extension Kmm_viewmodel_coreKMMViewModel: KMMViewModel { } but I receive this error in compile time Cannot find type 'Kmm_viewmodel_coreKMMViewModel' in scope

Thank you in advance.

ViewModel is deallocated immediately when used with NavigationStack

In the following example, the ViewModel is instantiated and passed to a NavigationPath first and later is set to a View.
Unfortunately this throws the following error:

KMMViewModelCore/ObservableViewModel.swift:34: Fatal error: ObservableViewModel has been deallocated

Is this caused by the path being @Published? Is there a way around it?

var body: some Scene {
        WindowGroup {
            NavigationStack(path: $navController.path) {
                RootView()
                    .navigationDestination(for: KMMViewModel.self) { destination in 
                        ViewFactory.viewForDestination(destination)
                    }
            }
            .environmentObject(navController)
        }
    }
class ViewFactory {
    @ViewBuilder
    static func viewForDestination(_ destination: KMMViewModel) -> some View {
        if let vm = destination as? TestViewModel {
            TestView(viewModel: vm)
        } else {
            EmptyView()
        }
    }
}

class NavController: ObservableObject {
I var path = NavigationPath()

    func show<VM>(_ vm: VM) where VM: KMMViewModel {
        path.append(vm)
    }
    ...
}
navController.show(TestViewModel() as KMMViewModel)

Cannot find type 'Kmm_viewmodel_coreKMMViewModel' in scope

Environment:
I defined the viewModel in shared module and exported the module to xcframework and imported to other ios app project.
It is work without calling any function in viewModel.
Then I added packages of KMMViewModel and also the KMM Native Coroutines.

However when I follow the readme that create the file KMMViewModel.swift

import KMMViewModelCore
import shared

extension Kmm_viewmodel_coreKMMViewModel: KMMViewModel { }

It cause Cannot find type 'Kmm_viewmodel_coreKMMViewModel' in scope.
How to solve this?

Question: Plans on providing SavedStateHandle and Parcelize integration?

First of all, many thanks for your efforts in creating KMM-ViewModel :)

Are there any plans on providing integration of any kind with SavedStateHandle and Parcelize? In our project we have our own custom implementation of shared navigation logic that is integrated in our KMMViewModels. Having SavedStateHandle + Parcelize would make us able to use it to pass arguments/results while navigating

Edit: though it would probably require integration with something like navController so I'm not sure if SavedStateHandle in KMMViewModel would help us at all. But could come in handy in some other cases also.

Cannot build because invalid JVM target

I'm using the latest Android Studio Canary and KMM/KMP plugins.
This won't compile and throws the following error.

class TestViewModel : KMMViewModel() {
    @NativeCoroutinesState
    var test = MutableStateFlow(viewModelScope, value = "Hello World!")
}
:shared:compileDebugKotlinAndroid
./TestViewModel.kt:9:16 Cannot inline bytecode built with JVM target 11 into bytecode that is being built with JVM target 1.8. Please specify proper '-jvm-target' option

I've tried to search for a solution and tried everything but nothing works.

How to create a Binding for SwiftUI?

Hey thanks for the great library! :)

I'm having issues with creating a Binding Variable for SwiftUI. My KMM code is the following:

class CommonAuthViewModel : KMMViewModel(), KoinComponent {
    @NativeCoroutinesState
    var isLoggedIn: StateFlow<Boolean> =
        MutableStateFlow(viewModelScope, false)

    fun changeState() {
            isLoggedIn.value = true
        }
}

In XCode I faced the following error:
image

In my view I initialized the ViewModel like the following:

    @StateViewModel var authViewModel = AuthViewModel()

I'm also confused from XCode it shows that this variable is a Binding
image

I want to thank you for this library and also thanking for the good communication with the community, that is not a matter of course and I appreciate that very much!

Only the first state is published with @StateViewModel

Hi.

I've been following @joreilly's Confetti sample and I have my shared view model defined like

class MyViewModel() : KMMViewModel() {
  val states: StateFlow<Int> = flow {
      var count = 1
      while (true) {
        delay(1000)
        println("EMITTING $count")
        emit(count++)
      }
    }
    .stateIn(this, SharingStarted.Eagerly, 0)
}

@NativeCoroutinesState
val MyViewModel.state: StateFlow<MainState> get() = states

This is consumed on the swift side like

import SwiftUI
import Combine
import KMMViewModelCore
import KMMViewModelSwiftUI
import KMPNativeCoroutinesCore

struct MyScreen: View {
  @StateViewModel private var viewModel = .init()  
  var body: some View {
    MyView(viewModel: $viewModel)
  }
}

struct MyView: MyView {
  @ObservedViewModel var viewModel: MyViewModel
  
  init(viewModel: ObservableViewModel<MyViewModel>.Projection) {
    self._viewModel = ObservedViewModel(viewModel)
  }
  
  var body: some View {
    let _ = print("STATE CONSUMED", viewModel.state)
  }
}

From the logger, it looks like only the first-ever state is consumed even though subsequent states are emitted

STATE CONSUMED 0
EMITTING 1
EMITTING 2
EMITTING 3
EMITTING 4
EMITTING 5

Is this the intended way to use @StateViewModel/@ObservedViewModel? Not sure if I'm missing something here.

KMP-NativeCoroutines and moko-resources conflict

Hello I have in viewmodel variables like
private val _loginState = MutableStateFlow<LoginStatus>(viewModelScope, LoginStatus.NonAuthenticated) @NativeCoroutinesState val loginState = _loginState.asStateFlow()

but in iOS when I have created viewmodel I can't have access to this variable, functions are visible, calling that functions is working well.
Can somebody help me where I have issue?
image

Xcode preview not working

Hello, awesome project!

I'm using this package together with NativeCoroutines and everything works great on both platforms but not in the Xcode preview. The preview will not load at all and crashes with this error log.

Error Log ------------------------------------- Translated Report (Full Report Below) -------------------------------------

Incident Identifier: 7471A224-E102-4E3A-8665-6BC7B1FD3D4A
CrashReporter Key: 8B36B5A8-1965-D9C2-C099-610507FD1EF2
Hardware Model: MacBookAir10,1
Process: ios [2276]
Path: /Users/USER/Library/Developer/Xcode/UserData/Previews/Simulator Devices/3D6F16D8-2B4B-47FC-A471-DED791F64349/data/Containers/Bundle/Application/8492D742-B9D7-4F60-AB2F-CD4098EE7443/ios.app/ios
Identifier: duck.hansson.odd.ios
Version: 1.0 (1)
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd_sim [72630]
Coalition: com.apple.CoreSimulator.SimDevice.3D6F16D8-2B4B-47FC-A471-DED791F64349 [16757]
Responsible Process: SimulatorTrampoline [64885]

Date/Time: 2023-05-17 20:01:11.3450 +0200
Launch Time: 2023-05-17 20:01:11.1845 +0200
OS Version: macOS 13.3.1 (22E261)
Release Type: User
Report Version: 104

Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Triggered by Thread: 0

Kernel Triage:
VM - (arg = 0x0) pmap_enter retried due to resource shortage
VM - (arg = 0x0) pmap_enter retried due to resource shortage
VM - (arg = 0x0) pmap_enter retried due to resource shortage
VM - (arg = 0x0) pmap_enter retried due to resource shortage
VM - (arg = 0x0) pmap_enter retried due to resource shortage

Thread 0 Crashed:: Dispatch queue: BSXPCCnx:com.apple.dt.xcode-previews.systemservices (BSCnx:client:com.apple.dt.uv.agent-preview-nonui-service)
0 libsystem_kernel.dylib 0x1b17e3fa8 __pthread_kill + 8
1 libsystem_pthread.dylib 0x1b183812c pthread_kill + 256
2 libsystem_c.dylib 0x18012873c abort + 124
3 Shared 0x103910cec konan::abort() + 12
4 Shared 0x10393ded8 (anonymous namespace)::terminateWithUnhandledException(ObjHeader*)::$_1::operator()() const + 16
5 Shared 0x10393dcf0 void (anonymous namespace)::$_0::operator()<(anonymous namespace)::terminateWithUnhandledException(ObjHeader*)::$_1>((anonymous namespace)::terminateWithUnhandledException(ObjHeader*)::$_1) + 80
6 Shared 0x10393da00 (anonymous namespace)::terminateWithUnhandledException(ObjHeader*) + 12
7 Shared 0x10393d9bc (anonymous namespace)::processUnhandledException(ObjHeader*) + 64
8 Shared 0x1039532b4 kotlin::ProcessUnhandledException(ObjHeader*) + 188
9 Shared 0x103955e2c Kotlin_ObjCExport_trapOnUndeclaredException + 36
10 Shared 0x1036e5b14 objc2kotlin_kfun:duck.hansson.odd.shared.module.SharedModule#(){}duck.hansson.odd.shared.viewmodel.HomeViewModel + 252
11 ios 0x102392240 ContentView.init() + 120 (ContentView.swift:13)
12 ContentView.1.preview-thunk.dylib 0x10369a09c static ContentView_Previews.__preview__previews.getter + 52 (ContentView.swift:28)
13 ios 0x10239270c protocol witness for static PreviewProvider.previews.getter in conformance ContentView_Previews + 12
14 SwiftUI 0x108fb9428 0x108610000 + 10130472
15 ios 0x102392750 protocol witness for static _PreviewProvider._previews.getter in conformance ContentView_Previews + 40
16 SwiftUI 0x108fb9cc0 0x108610000 + 10132672
17 PreviewsInjection 0x1024bf134 0x10248c000 + 209204
18 PreviewsInjection 0x1024bf098 0x10248c000 + 209048
19 PreviewsInjection 0x1024b5d08 0x10248c000 + 171272
20 PreviewsInjection 0x1024ae2d4 0x10248c000 + 139988
21 PreviewsInjection 0x1024add14 0x10248c000 + 138516
22 PreviewsInjection 0x1024adf2c 0x10248c000 + 139052
23 BoardServices 0x184a2116c +[BSXPCServiceConnectionProxy invokeMethod:onTarget:withMessage:forConnection:] + 1136
24 BoardServices 0x184a2ff48 __63-[BSXPCServiceConnectionEventHandler connection:handleMessage:]_block_invoke + 536
25 BoardServices 0x184a53fcc BSXPCServiceConnectionExecuteCallOut + 232
26 BoardServices 0x184a2fbe0 -[BSXPCServiceConnectionEventHandler connection:handleMessage:] + 148
27 BoardServices 0x184a53554 -[BSXPCServiceConnection _connection_handleMessage:fromPeer:withHandoff:] + 512
28 libdispatch.dylib 0x180132ee4 _dispatch_call_block_and_release + 24
29 libdispatch.dylib 0x180134708 _dispatch_client_callout + 16
30 libdispatch.dylib 0x18013c77c _dispatch_lane_serial_drain + 776
31 libdispatch.dylib 0x18013d414 _dispatch_lane_invoke + 448
32 libdispatch.dylib 0x180143e4c _dispatch_main_queue_drain + 824
33 libdispatch.dylib 0x180143b04 _dispatch_main_queue_callback_4CF + 40
34 CoreFoundation 0x18039a784 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE + 12
35 CoreFoundation 0x180394de4 __CFRunLoopRun + 1912
36 CoreFoundation 0x180394254 CFRunLoopRunSpecific + 584
37 GraphicsServices 0x188eb7c9c GSEventRunModal + 160
38 UIKitCore 0x1050b2ff0 -[UIApplication _run] + 868
39 UIKitCore 0x1050b6f3c UIApplicationMain + 124
40 SwiftUI 0x10956734c 0x108610000 + 16085836
41 SwiftUI 0x1095671ec 0x108610000 + 16085484
42 SwiftUI 0x108d5f474 0x108610000 + 7664756
43 ios 0x1023936a4 static iosApp.$main() + 40 (iosApp.swift:11)
44 ios 0x10239374c main + 12
45 dyld_sim 0x10257d514 start_sim + 20
46 dyld 0x102671f28 start + 2236

Thread 1:
0 libsystem_pthread.dylib 0x1b1833634 start_wqthread + 0

Thread 2:
0 libsystem_pthread.dylib 0x1b1833634 start_wqthread + 0

Thread 3:: GC Timer thread
0 libsystem_kernel.dylib 0x1b17df694 __psynch_cvwait + 8
1 libsystem_pthread.dylib 0x1b18389e4 _pthread_cond_wait + 1220
2 libc++.1.dylib 0x180280a84 std::__1::condition_variable::__do_timed_wait(std::__1::unique_lockstd::__1::mutex&, std::__1::chrono::time_point<std::__1::chrono::system_clock, std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000000000l>>>) + 96
3 Shared 0x10394bcbc void kotlin::RepeatedTimerkotlin::steady_clock::Run<kotlin::gc::internal::GCSchedulerDataWithTimerkotlin::steady_clock::GCSchedulerDataWithTimer(kotlin::gc::GCSchedulerConfig&, std::__1::function<void ()>)::'lambda'()>(kotlin::gc::internal::GCSchedulerDataWithTimerkotlin::steady_clock::GCSchedulerDataWithTimer(kotlin::gc::GCSchedulerConfig&, std::__1::function<void ()>)::'lambda'()&&) + 556
4 Shared 0x10394c4d4 void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_deletestd::__1::__thread_struct>, void ()(kotlin::ScopedThread::attributes, void (kotlin::RepeatedTimerkotlin::steady_clock::&&)(kotlin::gc::internal::GCSchedulerDataWithTimerkotlin::steady_clock::GCSchedulerDataWithTimer(kotlin::gc::GCSchedulerConfig&, std::__1::function<void ()>)::'lambda'()&&) noexcept, kotlin::RepeatedTimerkotlin::steady_clock&&, kotlin::gc::internal::GCSchedulerDataWithTimerkotlin::steady_clock::GCSchedulerDataWithTimer(kotlin::gc::GCSchedulerConfig&, std::__1::function<void ()>)::'lambda'()&&), kotlin::ScopedThread::attributes, void (kotlin::RepeatedTimerkotlin::steady_clock::)(kotlin::gc::internal::GCSchedulerDataWithTimerkotlin::steady_clock::GCSchedulerDataWithTimer(kotlin::gc::GCSchedulerConfig&, std::__1::function<void ()>)::'lambda'()&&) noexcept, kotlin::RepeatedTimerkotlin::steady_clock, kotlin::gc::internal::GCSchedulerDataWithTimerkotlin::steady_clock::GCSchedulerDataWithTimer(kotlin::gc::GCSchedulerConfig&, std::__1::function<void ()>)::'lambda'()>>(void) + 260
5 libsystem_pthread.dylib 0x1b1838428 _pthread_start + 116
6 libsystem_pthread.dylib 0x1b1833648 thread_start + 8

Thread 4:: GC thread
0 libsystem_kernel.dylib 0x1b17df694 __psynch_cvwait + 8
1 libsystem_pthread.dylib 0x1b18389e4 _pthread_cond_wait + 1220
2 libc++.1.dylib 0x1802809f4 std::__1::condition_variable::wait(std::__1::unique_lockstd::__1::mutex&) + 24
3 Shared 0x103944d8c std::__1::invoke_result<kotlin::gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep(kotlin::mm::ObjectFactorykotlin::gc::ConcurrentMarkAndSweep&, kotlin::gc::GCScheduler&)::$_3>::type kotlin::ScopedThread::Run<kotlin::gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep(kotlin::mm::ObjectFactorykotlin::gc::ConcurrentMarkAndSweep&, kotlin::gc::GCScheduler&)::$_3>(kotlin::ScopedThread::attributes, kotlin::gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep(kotlin::mm::ObjectFactorykotlin::gc::ConcurrentMarkAndSweep&, kotlin::gc::GCScheduler&)::$_3&&) + 300
4 Shared 0x103944f08 void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_deletestd::__1::__thread_struct>, void ()(kotlin::ScopedThread::attributes, kotlin::gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep(kotlin::mm::ObjectFactorykotlin::gc::ConcurrentMarkAndSweep&, kotlin::gc::GCScheduler&)::$_3&&), kotlin::ScopedThread::attributes, kotlin::gc::ConcurrentMarkAndSweep::ConcurrentMarkAndSweep(kotlin::mm::ObjectFactorykotlin::gc::ConcurrentMarkAndSweep&, kotlin::gc::GCScheduler&)::$_3>>(void) + 232
5 libsystem_pthread.dylib 0x1b1838428 _pthread_start + 116
6 libsystem_pthread.dylib 0x1b1833648 thread_start + 8

Thread 5:
0 libsystem_pthread.dylib 0x1b1833634 start_wqthread + 0

Thread 6:
0 libsystem_pthread.dylib 0x1b1833634 start_wqthread + 0

Thread 7:
0 libsystem_pthread.dylib 0x1b1833634 start_wqthread + 0

Thread 8:: com.apple.uikit.eventfetch-thread
0 libsystem_kernel.dylib 0x1b17dc190 mach_msg2_trap + 8
1 libsystem_kernel.dylib 0x1b17ed258 mach_msg2_internal + 76
2 libsystem_kernel.dylib 0x1b17e4398 mach_msg_overwrite + 540
3 libsystem_kernel.dylib 0x1b17dc500 mach_msg + 20
4 CoreFoundation 0x18039a4a8 __CFRunLoopServiceMachPort + 156
5 CoreFoundation 0x180394ad4 __CFRunLoopRun + 1128
6 CoreFoundation 0x180394254 CFRunLoopRunSpecific + 584
7 Foundation 0x180b994bc -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 208
8 Foundation 0x180b996e0 -[NSRunLoop(NSRunLoop) runUntilDate:] + 60
9 UIKitCore 0x105152714 -[UIEventFetcher threadMain] + 404
10 Foundation 0x180bbede0 NSThread__start + 704
11 libsystem_pthread.dylib 0x1b1838428 _pthread_start + 116
12 libsystem_pthread.dylib 0x1b1833648 thread_start + 8

Thread 0 crashed with ARM Thread State (64-bit):
x0: 0x0000000000000000 x1: 0x0000000000000000 x2: 0x0000000000000000 x3: 0x0000000000000000
x4: 0x0000600002b16a00 x5: 0x0000000000000000 x6: 0x0000000000000000 x7: 0x0000000000000000
x8: 0x0000000102708240 x9: 0x4055bf549d901fe4 x10: 0x000000016da6ee58 x11: 0x0000000158104558
x12: 0x0000000000000000 x13: 0x00000000000007fd x14: 0x00000000a5428870 x15: 0x00000000a5228072
x16: 0x0000000000000148 x17: 0x0000600002c100f0 x18: 0x0000000000000000 x19: 0x0000000000000006
x20: 0x0000000102708240 x21: 0x0000000000000103 x22: 0x0000000102708320 x23: 0x0000000102392700
x24: 0x000000016da6fd10 x25: 0x00000001023a8738 x26: 0x00000001bc8f5978 x27: 0x4000600000108d20
x28: 0x00000001bc8f5918 fp: 0x000000016da6f870 lr: 0x00000001b183812c
sp: 0x000000016da6f850 pc: 0x00000001b17e3fa8 cpsr: 0x40001000
far: 0x000004fdd18e4100 esr: 0x56000080 Address size fault

Binary Images:
0x10266c000 - 0x1026fbfff dyld () /usr/lib/dyld
0x10257c000 - 0x1025cbfff dyld_sim (
) <4eba7f04-0a30-3166-8a68-9125b8a1d5f9> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/dyld_sim
0x10238c000 - 0x1023a7fff duck.hansson.odd.ios (1.0) <58777694-be21-3b1c-8b91-846ec6038bd7> /Users/USER/Library/Developer/Xcode/UserData/Previews/Simulator Devices/3D6F16D8-2B4B-47FC-A471-DED791F64349/data/Containers/Bundle/Application/8492D742-B9D7-4F60-AB2F-CD4098EE7443/ios.app/ios
0x10248c000 - 0x1024d3fff com.apple.dt.PreviewsInjection (14.3) <2b6de7fe-add3-34d3-beb7-5907656cc116> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/PreviewsInjection.framework/PreviewsInjection
0x1036b4000 - 0x103ccffff duck.hansson.odd.shared.Shared (1.0) /Users/USER/Library/Developer/Xcode/UserData/Previews/Simulator Devices/3D6F16D8-2B4B-47FC-A471-DED791F64349/data/Containers/Bundle/Application/8492D742-B9D7-4F60-AB2F-CD4098EE7443/ios.app/Frameworks/Shared.framework/Shared
0x108610000 - 0x109bd3fff com.apple.SwiftUI (4.4.36.1.102) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/SwiftUI.framework/SwiftUI
0x102a10000 - 0x102a77fff libswiftUIKit.dylib () /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift/libswiftUIKit.dylib
0x102534000 - 0x102537fff com.apple.UIKit (1.0) <7e2b5d6b-224b-39a1-af92-5f3909a01490> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit
0x1028b4000 - 0x1028fbfff com.apple.DocumentManager (1.0) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/DocumentManager.framework/DocumentManager
0x104634000 - 0x105d17fff com.apple.UIKitCore (1.0) <6726ae46-2599-3f92-adca-c48a0512d4ea> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore
0x102fd0000 - 0x1030bffff com.apple.ShareSheet (1885) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/ShareSheet.framework/ShareSheet
0x102764000 - 0x1027effff com.apple.PrintKitUI (1.0) <74ff1df9-3f27-335a-9673-8991789c7e21> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/PrintKitUI.framework/PrintKitUI
0x102b6c000 - 0x102d03fff com.apple.WebKitLegacy (8615) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Cryptexes/OS/System/Library/PrivateFrameworks/WebKitLegacy.framework/WebKitLegacy
0x10de3c000 - 0x10f2f3fff com.apple.JavaScriptCore (8615) <6c92ecb5-f484-3925-b8ac-132e313d11cc> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Cryptexes/OS/System/Library/Frameworks/JavaScriptCore.framework/JavaScriptCore
0x117588000 - 0x119d5ffff com.apple.WebCore (8615) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Cryptexes/OS/System/Library/PrivateFrameworks/WebCore.framework/WebCore
0x10a700000 - 0x10aa67fff libANGLE-shared.dylib (
) <094a5d7d-536f-3c65-bf0f-42037de47f30> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Cryptexes/OS/System/Library/PrivateFrameworks/WebCore.framework/Frameworks/libANGLE-shared.dylib
0x102964000 - 0x1029bbfff com.apple.WebGPU (8615) <84b58cce-be36-3c7e-8f54-801c8cb3c7fa> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Cryptexes/OS/System/Library/PrivateFrameworks/WebGPU.framework/WebGPU
0x1077cc000 - 0x10821ffff libwebrtc.dylib () /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Cryptexes/OS/System/Library/PrivateFrameworks/WebCore.framework/Frameworks/libwebrtc.dylib
0x102610000 - 0x102623fff com.apple.RecapPerformanceTesting (17) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/RecapPerformanceTesting.framework/RecapPerformanceTesting
0x10ac74000 - 0x10addffff com.apple.chronokit (1.0) <646ed4dc-664b-37b1-99c5-350381e4621b> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/ChronoKit.framework/ChronoKit
0x10aeb8000 - 0x10afd7fff com.apple.widgetkit (1.0) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/WidgetKit.framework/WidgetKit
0x102544000 - 0x10255bfff com.apple.dt.PreviewsOSSupportUI (14.3) <5bab1eb4-c81c-3e3a-8edc-b15bcacdb085> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/PreviewsOSSupportUI.framework/PreviewsOSSupportUI
0x102f68000 - 0x102f93fff com.apple.dt.PreviewsServicesUI (14.3) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/PreviewsServicesUI.framework/PreviewsServicesUI
0x102ebc000 - 0x102ee3fff com.apple.BaseBoardUI (617.107) <173814de-7bee-3fd3-a6cc-3fd9f89a0bd1> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/BaseBoardUI.framework/BaseBoardUI
0x1032dc000 - 0x103303fff com.apple.chronouiservices (1.0) <0d419660-ac2f-30c9-8a2c-99f6052e7224> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/ChronoUIServices.framework/ChronoUIServices
0x103344000 - 0x10335ffff com.apple.MaterialKit (1.0) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/MaterialKit.framework/MaterialKit
0x103214000 - 0x10326bfff com.apple.internal.ActivityUIServices (1.0) <4c169598-dc07-3244-8fd2-c66a18c93f5d> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/ActivityUIServices.framework/ActivityUIServices
0x1034d4000 - 0x103517fff com.apple.PlatterKit (1.0) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/PlatterKit.framework/PlatterKit
0x103434000 - 0x10344bfff libswiftExtensionKit.dylib (
) <3b6af021-32be-3dfc-9feb-cca7b5deb936> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift/libswiftExtensionKit.dylib
0x102f1c000 - 0x102f33fff com.apple.ExtensionKit (97) <7c6fe311-0f0c-32d2-b323-5f0e25540283> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/ExtensionKit.framework/ExtensionKit
0x10b588000 - 0x10b697fff com.apple.preferences-framework (1) <57984a86-c974-3534-8fa5-c3d2e29b4be7> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/Preferences.framework/Preferences
0x10b370000 - 0x10b40bfff com.apple.BacklightServicesHost (1.0) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/BacklightServicesHost.framework/BacklightServicesHost
0x103484000 - 0x103497fff com.apple.settingsandcoreapps.SettingsFoundation (1.0) <6b8b3380-36ef-39a4-9171-22a55f2dc229> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/SettingsFoundation.framework/SettingsFoundation
0x103418000 - 0x103423fff libobjc-trampolines.dylib () <806ae646-0aee-359b-883f-e4018780ec94> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/libobjc-trampolines.dylib
0x103698000 - 0x10369bfff ContentView.1.preview-thunk.dylib (
) /Users/USER/Library/Developer/Xcode/DerivedData/ios-gqblrasochcoajatuyocronxorua/Build/Intermediates.noindex/Previews/ios/Intermediates.noindex/ios.build/Debug-iphonesimulator/ios.build/Objects-normal/arm64/ContentView.1.preview-thunk.dylib
0x1b17db000 - 0x1b1812fe7 libsystem_kernel.dylib () /usr/lib/system/libsystem_kernel.dylib
0x1b1831000 - 0x1b183dff7 libsystem_pthread.dylib (
) <73f649ed-142d-3aad-b3af-3fe33548b725> /usr/lib/system/libsystem_pthread.dylib
0x1800b6000 - 0x180130ffb libsystem_c.dylib () <6b3ced39-3f0d-3bb2-a2b1-34062cf5c8eb> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/system/libsystem_c.dylib
0x184a1c000 - 0x184a6efff com.apple.BoardServices (1.0) <170a1733-6f68-37eb-bc4b-1379497bffc0> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/BoardServices.framework/BoardServices
0x180131000 - 0x180177fff libdispatch.dylib (
) <392b7c55-8c38-3dea-b7af-c3d7f518e987> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/system/libdispatch.dylib
0x180315000 - 0x1806c3fff com.apple.CoreFoundation (6.9) <132e87d0-14ac-310c-a5e9-3d9c921cc8ea> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
0x188eb4000 - 0x188ebcfff com.apple.GraphicsServices (1.0) <8e24edb2-1c99-3652-9ef0-e66191675515> /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/GraphicsServices.framework/GraphicsServices
0x18026e000 - 0x1802efff7 libc++.1.dylib (*) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/libc++.1.dylib
0x18073a000 - 0x180f8bfff com.apple.Foundation (6.9) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Foundation.framework/Foundation

EOF

The code that crashed the preview is this view binding of a view model. If it's removed the preview works.

struct ContentView: View {
    @ObservedViewModel var viewModel = SharedModule().homeViewModel
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text(viewModel.state.query)
        }
        .padding()
    }
}

I'm using Koin for dependency injection and I've confirmed it's not the issue as this code works in the preview.

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text(SharedModule().homeViewModel.state.query)
        }
        .padding()
    }
}

Question: Support for Compose Multiplatform Mobile

Compose for iOS just got Alpha yesterday. Is there any way that this library will support
shared view model that can be used in the shared module in compose Multiplatform without having to access it in native
Android (jetpack compose and android lifecycles) and iOS (swift and swift ui)

So, is there any chance this project will continue with those changes?

Also see this

Uncaught Kotlin exception: kotlin.IllegalStateException: KMMViewModel can't be wrapped more than once

Local setup

  • Kotlin Multiplatform 1.8.10
  • Kotlin Coroutines 1.6.4
  • KMM-ViewModel 1.0.0-ALPHA-4
  • KMM-NativeCoroutines 1.0.0-ALPHA-5

Description

I tried KMM-ViewModel with a basic example of a ViewModel usage, but I get the following error. Not sure if this is something I'm doing or an incompatibility with the current build.

Uncaught Kotlin exception: kotlin.IllegalStateException: KMMViewModel can't be wrapped more than once
    at 0   iosApp                              0x109c2c99b        kfun:kotlin.Throwable#<init>(kotlin.String?){} + 107 
    at 1   iosApp                              0x109c262c7        kfun:kotlin.Exception#<init>(kotlin.String?){} + 103 
    at 2   iosApp                              0x109c26627        kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 103 
    at 3   iosApp                              0x109c26b47        kfun:kotlin.IllegalStateException#<init>(kotlin.String?){} + 103 
    at 4   iosApp                              0x109e7c359        kfun:com.rickclephas.kmm.viewmodel.ViewModelScopeImpl#objc:setSendObjectWillChange: + 265 
    at 5   iosApp                              0x109e7cc44        _636f6d2e7269636b636c65706861732e6b6d6d3a6b6d6d2d766965776d6f64656c2d636f72652f55736572732f72756e6e65722f776f726b2f4b4d4d2d566965774d6f64656c2f4b4d4d2d566965774d6f64656c2f6b6d6d2d766965776d6f64656c2d636f72652f7372632f6170706c654d61696e2f6b6f746c696e2f636f6d2f7269636b636c65706861732f6b6d6d2f766965776d6f64656c2f566965774d6f64656c53636f70652e6b74_knbridge34 + 292 

Code

iOS:

struct ContentView: View {
    @StateViewModel private var contentViewModel = NotesViewModel()
    
    var body: some View {
        VStack {
            AddNoteView(contentViewModel: contentViewModel)
            NotesView(contentViewModel: contentViewModel)
        }
    }
}
...

shared:

class NotesViewModel: KMMViewModel() {

    private val noteRepository = NoteRepository()

    @NativeCoroutinesState
    val notes = noteRepository.todoList
        .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
...

Nullable Swift bindings

Given the following property in a Kotlin view model class:

@NativeCoroutineState
val price: MutableStateFlow<Double>

it is exposed to Swift as:

Binding<Double>

But if I make it nullable:

@NativeCoroutineState
val price: MutableStateFlow<Double?>

I get this in Swift:

Binding<KotlinDouble?>

which requires ugly conversion on the Swift side.

Is there something I can do to improve this?

Boolean toggle doesn't re-render swift view

I'm playing around a bit with KMM-Viewmodel and SwiftUI and for example sharing state for a TextField works great but updating a boolean does not re-render the view in SwiftUI.

This is my example ViewModel that holds the showPassword boolean and some extra state.

open class LoginViewModel : KMMViewModel() {
    var email: String = ""
    var password: String = ""
    var showPassword: Boolean = false

    fun changeEmail(email: String) {
        this.email = email
    }

    fun changePassword(password: String) {
        this.password = password
    }

    fun toggleShowPassword() {
        showPassword = !showPassword
    }
}

This is how I toggle the showPassword boolean in my iOS app and I expected the view to re-render and the two TextFields and the icon to switch but that doesn't happen.

...
VStack(spacing: Padding.l) {
    TextField(
        "Email",
         text: $loginViewModel.email
    )

    Group {
        if loginViewModel.showPassword {
            TextField(
              "Password",
              text: Binding(get: {loginViewModel.password}, set: loginViewModel.changePassword)
            )
        } else {
            SecureField(
                "Password",
                text: Binding(get: {loginViewModel.password}, set: loginViewModel.changePassword)
            )
        }
    }

    Button(
        action: { loginViewModel.toggleShowPassword() },
        label: {
            Image(systemName: loginViewModel.showPassword ? "eye" : "eye.slash")
        }
    )
}

Is this behaviour expected and just not yet supported or am I doing something wrong?

Mapping of shared ViewModel UI State in Swift not working

Hello!
I need to map a list of items that come from the shared View Model into objects that implement the Identifiable protocol (needed for showing them in a List or pass them to Maps and display markers).
This is the exposed state from the ViewModel in Kotlin:

// ui state data class
data class HomeUiState(
    val isLoading: Boolean = true,
    val closeVenues: List<Venue> = emptyList(),
    val errorMessage: String? = null
)

// state defined in ViewModel.kt inside shared module
    @NativeCoroutinesState
    val uiState = _uiState.asStateFlow().stateIn(
        viewModelScope, SharingStarted.WhileSubscribed(), HomeUiState()
    )

And, in my iOS app, I'm trying to map it like this:

class HomeViewModel : shared.HomeViewModel {
    
    @Published var venues: [UiVenue] = []
    
    override init() {
        super.init()
        venues = uiState.closeVenues.compactMap { venue in
            venue.toUiVenue() // the UiVenue struct implements Identifiable protocol
        }
    }
    
}

...

// ContentView.swift

@ObservedViewModel var viewModel = HomeViewModel()

var body: some View {

            List(viewModel.venues) { venue in
                Text("Hello \(venue.name)").foregroundColor(Color.black)
            }.onAppear {
                 viewModel.getCloseVenues()
            }

}

However, the list is not rendering. I've tried using @State also, but nothing happens. The uiState.closeVenues returns a simple [Venue]object, not a Combine Observable.

However, if I use directly viewModel.uiState.closeVenues from the view, it works fine 🤔

Environment

  • Kotlin: 1.8.0
  • commonMain KMMViewModel dependency: com.rickclephas.kmm:kmm-viewmodel-core:1.0.0-ALPHA-3
  • nativeCoroutines: 1.0.0-ALPHA-4
  • ksp: 1.8.0-1.0.9

PS: this library is great, it has saved me a lot of time

kotlin target version is 1.6.21

At present, my project supports up to Kotlin 1.6.21 version. Due to the upgrade of Kotlin version, it has a great impact on the project. Can you provide a version of Kotlin 1.6.21?

Ideal way of updating state from a suspending function

First off all, KMM-Viewmodel is great and even better in combination with KMP-NativeCoroutines, so thanks a lot for creating both.

I'm currently trying to update a MutableStateFlow inside a suspending function, but doing that gives me the following warning since updates to the ui need to be done on the main thread:

[SwiftUI] Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.

So here is my suspending function and how I call it:

class LoginViewModel : KMMViewModel() {
    @NativeCoroutinesState
    var loginLoading = MutableStateFlow(viewModelScope, false)
    
    suspend fun login() {
        loginLoading.update { true }
        val res = loginUseCase(email.value, password.value)
        res.fold(
            onSuccess = {
                appViewModel.dismissAuthFlow()
                appViewModel.setAccount(it)
            },
            onFailure = {
                appViewModel.setAccount(null)
            }
        )
        loginLoading.update { false }
    }
}

In SwiftUI I use a Task to run the async function:

Button(
    action: {
        Task {
            try await viewModel.login()
        }
    },
    label: {
        ZStack {
            if viewModel.loginLoading {
                ProgressView()
                    .colorInvert()
            } else {
                Text("Log in")
            }
        }
    }
)

I have experimented a bit on how to get it to work and wrapping all updates to the ui with withContext(Dispatchers.Main) does the trick. The working suspending function would look like this:

suspend fun login() {
    withContext(Dispatchers.Main) {
        loginLoading.update { true }
    }
    val res = loginUseCase(email.value, password.value)
    withContext(Dispatchers.Main) {
        res.fold(
            onSuccess = {
                appViewModel.dismissAuthFlow()
                appViewModel.setAccount(it)
            },
            onFailure = {
                appViewModel.setAccount(null)
            }
        )
        loginLoading.update { false }
    }
}

Is there a better way to update the ui from a suspending function than manually dispatching the updates on the main thread, to me it looks like this could be handled by the MutableStateFlow implementation for iOS.

Storing ViewModel in parent-view and wrapping in child throws error on rerender of child

When creating a ViewModel in a parent view and passing the reference to a child view which wraps it with any of the property wrappers from KMM-Viewmodel, the app crashes on a rerender of the child view with the error:

Uncaught Kotlin exception: kotlin.IllegalStateException: KMMViewModel can't be wrapped more than once

I know that the error is intentionally thrown since the stored ViewModel is already wrapped and not recreated on rerender of the child, but to me this looks like a common pattern that should be supported.
Why is it currently not supported and is it technically possible?

This is my setup, where the parent handles navigation between some screens and also holds the ViewModels for each screen.
I don't want to recreate the ViewModels on each recreation of the view, as I want to save the state between rerenders.

class NavigationViewModel: ObservableObject {
    @Published var destination: Destination = .home
    
    let mapVM = MapViewModel()
    
    enum Destination: Hashable {
        case home
        case map(MapViewModel)
        case profile
    }
}

struct MainNavigationView: View {
    @ObservedObject var viewModel: MainNavigationViewModel
    
    var body: some View {
        VStack {
            Group {
                // this is from the swiftui-navigation library, it just shows the view for the current `destination`
                IfCaseLet($viewModel.destination, pattern: /MainNavigationViewModel.Destination.home) { $vm in
                    HomeView()
                }
                
                IfCaseLet($viewModel.destination, pattern: /MainNavigationViewModel.Destination.map) { $vm in
                    MapView(
                        viewModel: vm
                    )
                }
                
                IfCaseLet($viewModel.destination, pattern: /MainNavigationViewModel.Destination.profile) { $vm in
                    ProfileView()
                }
            }
            .frame(maxWidth: .infinity,maxHeight: .infinity)
        }
    }
}

The Subview wraps the passed ViewModel with ObservedViewModel

struct MapView: View {
    
    @ObservedViewModel var viewModel: MapViewModel
    
    var body: some View {
        Text("SubView")
    }
}

Supertypes of the following classes cannot be resolved

I cannot implement ViewModel on Android. iOS works fine. Tried it on ALPHA-12 and ALPHA-10.

Error

Supertypes of the following classes cannot be resolved. Please make sure you have the required dependencies in the classpath:
    class me.blanik.sample.SignInViewModel, unresolved supertypes: com.rickclephas.kmm.viewmodel.KMMViewModel
Adding -Xextended-compiler-checks argument might provide additional information.

Shared gradle

plugins {
    kotlin("multiplatform")
    kotlin("native.cocoapods")
    id("com.android.library")
    id("com.google.devtools.ksp") version "1.9.0-1.0.11"
    id("com.rickclephas.kmp.nativecoroutines") version "1.0.0-ALPHA-13"
    kotlin("plugin.serialization") version "1.7.20"
}

@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
kotlin {
    androidTarget()
    jvmToolchain(11)

    iosX64()
    iosArm64()
    iosSimulatorArm64()

    cocoapods {
        summary = "Some description for the Shared Module"
        homepage = "Link to the Shared Module homepage"
        version = "1.0"
        ios.deploymentTarget = "14.1"
        podfile = project.file("../iosApp/Podfile")
        framework {
            baseName = "shared"
        }
    }

    sourceSets {
        val multiplatformSettingsVersion = "1.0.0"
        val kmmViewModelVersion = "1.0.0-ALPHA-12"

        all {
            languageSettings.optIn("kotlin.experimental.ExperimentalObjCName")
        }

        val commonMain by getting {
            dependencies {
                implementation("com.russhwolf:multiplatform-settings-no-arg:$multiplatformSettingsVersion")
                implementation("com.russhwolf:multiplatform-settings-serialization:$multiplatformSettingsVersion")
                implementation("com.russhwolf:multiplatform-settings-coroutines:$multiplatformSettingsVersion")
                implementation("com.rickclephas.kmm:kmm-viewmodel-core:$kmmViewModelVersion")
            }
        }

        val commonTest by getting {
            dependencies {
                implementation(kotlin("test"))
            }
        }

        val androidMain by getting {
            dependencies {
                implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
            }
        }
        val androidUnitTest by getting
        val iosX64Main by getting
        val iosArm64Main by getting
        val iosSimulatorArm64Main by getting
        val iosMain by creating {
            dependsOn(commonMain)
            iosX64Main.dependsOn(this)
            iosArm64Main.dependsOn(this)
            iosSimulatorArm64Main.dependsOn(this)

            dependencies {
            }
        }
        val iosX64Test by getting
        val iosArm64Test by getting
        val iosSimulatorArm64Test by getting
        val iosTest by creating {
            dependsOn(commonTest)
            iosX64Test.dependsOn(this)
            iosArm64Test.dependsOn(this)
            iosSimulatorArm64Test.dependsOn(this)
        }
    }
}

android {
    namespace = "me.blanik.sample"
    compileSdk = 33
    defaultConfig {
        minSdk = 28
    }

    // @TODO: Remove workaround for https://issuetracker.google.com/issues/260059413
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
}

Android Implementation:

package me.blanik.sample.android

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import me.blanik.sample.Greeting
import me.blanik.sample.SignInViewModel

class MainActivity : ComponentActivity() {
    private val viewModel: SignInViewModel() by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    GreetingView(Greeting().greet())
                    GreetingView(viewModel.email.value)
                }
            }
        }
    }
}

@Composable
fun GreetingView(text: String) {
    Text(text = text)
}

@Preview
@Composable
fun DefaultPreview() {
    MyApplicationTheme {
        GreetingView("Hello, Android!")
    }
}

Kotlin Shared:

package me.blanik.sample

import com.rickclephas.kmm.viewmodel.*
import kotlinx.coroutines.flow.*

open class SignInViewModel: KMMViewModel() {
    private val _email = MutableStateFlow(viewModelScope, "")
    private val _password = MutableStateFlow(viewModelScope, "")

    val email = _email.asStateFlow()
    val password = _password.asStateFlow()

    fun setEmail(email: String) {
        _email.value = email
    }

    fun setPassword(password: String) {
        _password.value = password
    }
}

Unresolved supertypes: androidx.lifecycle.ViewModel

I've used the 1.0.0-ALPHA-7 version of the library and followed the README to define a view model in my shared KMM module. When building the project I get the following error:
Supertypes of the following classes cannot be resolved. Please make sure you have the required dependencies in the classpath: class com.rickclephas.kmm.viewmodel.KMMViewModel, unresolved supertypes: androidx.lifecycle.ViewModel

I'm not sure why I'm getting this error because as far as I can tell the KMM-ViewModel transitively adds the dependency for the Android ViewModel.

KMMViewModel being `clear`ed unexpectedly

I'm getting a lot of errors in production relating to ktor calls being cancelled in the viewmodel when the viewModelScope is cleared.

I have the following stack trace:

Non-fatal Exception: io.github.jan.supabase.exceptions.HttpRequestException
HTTP request to http://localhost?user_id=eq.<user_id>&reading_status=eq.FINISHED&finish_date=gte.2024-01-01T00%3A00&select=%2A (GET) failed with message: Job was cancelled

io.github.jan.supabase.network.KtorSupabaseHttpClient.request (KtorSupabaseHttpClient.kt:55)
io.github.jan.supabase.network.KtorSupabaseHttpClient$request$1.invokeSuspend (KtorSupabaseHttpClient.kt:12)
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:102)
kotlinx.coroutines.EventLoop.processUnconfinedEvent (EventLoop.common.kt:65)
kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined (DispatchedTask.kt:241)
kotlinx.coroutines.DispatchedTaskKt.dispatch (DispatchedTask.kt:159)
kotlinx.coroutines.CancellableContinuationImpl.dispatchResume (CancellableContinuationImpl.kt:470)
kotlinx.coroutines.CancellableContinuationImpl.cancel (CancellableContinuationImpl.kt:213)
kotlinx.coroutines.CancellableContinuationImpl.parentCancelled$kotlinx_coroutines_core (CancellableContinuationImpl.kt:220)
kotlinx.coroutines.ChildContinuation.invoke (JobSupport.kt:1447)
kotlinx.coroutines.JobSupport.notifyCancelling (JobSupport.kt:1473)
kotlinx.coroutines.JobSupport.tryMakeCancelling (JobSupport.kt:796)
kotlinx.coroutines.JobSupport.makeCancelling (JobSupport.kt:756)
kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core (JobSupport.kt:672)
kotlinx.coroutines.JobSupport.parentCancelled (JobSupport.kt)
kotlinx.coroutines.ChildHandleNode.invoke (JobSupport.kt:1436)
kotlinx.coroutines.JobSupport.notifyCancelling (JobSupport.kt:1473)
kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath (JobSupport.kt:901)
kotlinx.coroutines.JobSupport.tryMakeCompleting (JobSupport.kt:864)
kotlinx.coroutines.JobSupport.cancelMakeCompleting (JobSupport.kt:697)
kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core (JobSupport.kt:668)
kotlinx.coroutines.JobSupport.cancelInternal (JobSupport.kt)
kotlinx.coroutines.JobSupport.cancel (JobSupport.kt:618)
kotlinx.coroutines.JobKt__JobKt.cancel (Job.kt:560)
kotlinx.coroutines.JobKt.cancel (Job.kt)
kotlinx.coroutines.JobKt__JobKt.cancel$default (Job.kt:559)
kotlinx.coroutines.JobKt.cancel$default (Job.kt)
androidx.lifecycle.CloseableCoroutineScope.close (ViewModel.kt:51)
androidx.lifecycle.ViewModel.closeWithRuntimeException (ViewModel.kt:252)
androidx.lifecycle.ViewModel.clear (ViewModel.java:189)
androidx.lifecycle.ViewModelStore.clear (ViewModelStore.kt:69)
androidx.navigation.NavControllerViewModel.clear (NavControllerViewModel.kt:33)
androidx.navigation.NavController$NavControllerNavigatorState.markTransitionComplete (NavController.kt:359)
androidx.navigation.NavController.unlinkChildFromParent$navigation_runtime_release (NavController.kt:163)
androidx.navigation.NavController$NavControllerNavigatorState.markTransitionComplete (NavController.kt:352)
androidx.navigation.compose.ComposeNavigator.onTransitionComplete (ComposeNavigator.kt:82)
androidx.navigation.compose.NavHostKt$NavHost$15.invokeSuspend (NavHost.kt:314)
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:104)
androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch (AndroidUiDispatcher.android.kt:81)
androidx.compose.ui.platform.AndroidUiDispatcher.access$setScheduledFrameDispatch$p (AndroidUiDispatcher.android.kt)
androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch (AndroidUiDispatcher.android.kt)
androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.run (AndroidUiDispatcher.android.kt:57)
android.os.Handler.handleCallback (Handler.java:942)
android.os.Handler.dispatchMessage (Handler.java:99)
android.os.Looper.loopOnce (Looper.java:226)
android.os.Looper.loop (Looper.java:313)
android.app.ActivityThread.main (ActivityThread.java:8757)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:571)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1067)

These lines in particular make it look like the ViewModel is being cleared, and the coroutine that launches the database call is being cancelled and the data is lost:

androidx.lifecycle.ViewModel.closeWithRuntimeException (ViewModel.kt:252)
androidx.lifecycle.ViewModel.clear (ViewModel.java:189)
androidx.lifecycle.ViewModelStore.clear (ViewModelStore.kt:69)
androidx.navigation.NavControllerViewModel.clear (NavControllerViewModel.kt:33)
androidx.navigation.NavController$NavControllerNavigatorState.markTransitionComplete (NavController.kt:359)
androidx.navigation.NavController.unlinkChildFromParent$navigation_runtime_release (NavController.kt:163)
androidx.navigation.NavController$NavControllerNavigatorState.markTransitionComplete (NavController.kt:352)
androidx.navigation.compose.ComposeNavigator.onTransitionComplete (ComposeNavigator.kt:82)
androidx.navigation.compose.NavHostKt$NavHost$15.invokeSuspend (NavHost.kt:314)

Users are seeing this on app startup on the home screen, and seemingly randomly. Some have reported that killing and restarting the app fixes it temporarily. Some have reinstalled and said that works for a little while, but the issue seems to be happening intermittently and I'm not able to reproduce it consistently on my end.

Any idea what might be causing this?

On iOS state is not updating the views.

Hi! Thanks for creating KMMViewModel! 🙇🏼

I have started using KMMViewModel like this, also I have setup the extension for Kmm_viewmodel_coreKMMViewModel like mentioned in the docs.

class LoginViewModel() : KMMViewModel() {
    @NativeCoroutinesState
    val uiState = MutableStateFlow(LoginUIState())
}

on iOS I was able to initialise the VM in my View

struct LoginScreen: View {
    @ObservedViewModel var viewModel : LoginViewModel

    init() {
        self.viewModel = LoginViewModel()
    }

    var body: some View {
          viewModel.uiState // this never updates
      }
}

The problem is that the uiState never updates even if the ViewModel emits new values to the state flow , am i doing something wrong here ?

Is it possible to use interfaces of ViewModel in Swift?

Interfaces are useful in case of faking in previews.
For example:

interface ItemViewModel {
    @NativeCoroutinesState val title: MutableStateFlow<String>
    @NativeCoroutinesState val isEditing: StateFlow<Boolean>
    @NativeCoroutinesState val canSave: StateFlow<Boolean>
}

class ItemViewModelImpl(
    private val heavyDependency1: HeavyDependency1,
    private val heavyDependency2: HeavyDependency2,
    ...
    private val heavyDependency101: HeavyDependency101,
): KMMViewModel(), ItemViewModel {
    override val title = MutableStateFlow("")
    override val isEditing = MutableStateFlow(false)
    override val canSave = MutableStateFlow(false)
}

class ItemViewModelFake(
    title: String,
    isEditing: Boolean = false,
    canSave: Boolean = false,
): KMMViewModel(), ItemViewModel {
    override val title = MutableStateFlow(title)
    override val isEditing = MutableStateFlow(isEditing)
    override val canSave = MutableStateFlow(canSave)
}

@Composable
fun Item(viewModel: ViewModel /*for the sake of simplicity without DI*/) {
    // some UI
}

And use them for previews:

@Preview
@Composable
private fun ItemPreview() {
    MaterialTheme {
        Item(
            ViewModelFake(title = "123")
        )
    }
}

@Preview
@Composable
private fun ItemEditingCannotSavePreview() {
    MaterialTheme {
        Item(
            ViewModelFake(title = "123", isEditing = true)
        )
    }
}

@Preview
@Composable
private fun ItemEditingCanSavePreview() {
    MaterialTheme {
        Item(
            ViewModelFake(title = "123", isEditing = true, canSave = true)
        )
    }
}

What about SwiftUI?

KMM viewModelScope.coroutineScope.launch body never called

Hi!
I just switch to KMM ViewModel to make multiplatform viewmodels, and I encountered this weird issue:

On Android, to dispatch network requests I normally use:

viewModelScope.launch {
  println("Hello")
}

So it's logical to replace it by this using KMM ViewModel:

viewModelScope.coroutineScope.launch {
  println("Hello")
}

But the body of the launch call is never executed... (I first had real network requests but added prints and understood that they were not called with KMM implementation)
I found a workaround using GlobalScope.launch which works, but might cause lifecycle issues, so I would prefer to get a working viewModelScope.coroutineScope.launch.

I looked at the native implementation of Android coroutineScope and the difference I see is that on KMM ViewModel we have:

CoroutineScope(SupervisorJob() + Dispatchers.Main)

While on AndroidX it's implemented this way:

CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)

So this might be a place to look for a fix.
Anyone else is encountering this issue?

Can't observe the same viewModel object in two different screens on iOS

I'm using Koin DI to inject singleton ViewModels into my iOS app - I have the following LoginScreen defined:

struct LoginScreen: View {
    @ObservedViewModel var viewModel: LoginViewModel = Koin.instance.get()

    ...
}

I created a SplashScreen that also depends on the LoginViewModel, but this code fails with the error kotlin.IllegalStateException: KMMViewModel can't be wrapped more than once

struct SplashScreen: View {
    @ObservedViewModel var loginViewModel: LoginViewModel = Koin.instance.get()
    
    ...
}

If I make the ViewModel manually (so it's not the same object as the one in LoginScreen), it doesn't get the error when run, but loses the benefit of being a singleton between the two screens.

struct SplashScreen: View {
    @ObservedViewModel var loginViewModel: LoginViewModel = LoginViewModel(authRepository: Koin.instance.get())
    
    ...
}

There are a couple options here - I could pass the ViewModel from one screen to the next, but that tightly couples one view to the one before it, which I don't want. I could also create a SplashScreenViewModel and use that, but there are other places in my app that are going to need to share instances of the same ViewModel, so this would become an issue again very quickly.

This is not an issue on Android, you can inject a singleton ViewModel into as many screens as you want without getting this error.

Is this something that could be fixed at the library level, or is there another way to access singleton ViewModels in multiple screens?

koin

First of all, awesome work with NativeCoroutines and this package.

Question: Build Error

Hi Rick. I'm trying to use Kmm-ViewModel in my project. I am getting the following error. Any idea how I can resolve this?

Supertypes of the following classes cannot be resolved. Please make sure you have the required dependencies in the classpath:
    class com.rickclephas.kmm.viewmodel.KMMViewModel, unresolved supertypes: androidx.lifecycle.ViewModel
Adding -Xextended-compiler-checks argument might provide additional information.

Edited:

I figured it out. I was missing viewmodel dependency in the android module.

ViewModel constructor parameters on iOS

Hey,
on android side Koin DI allows passing constructor parameters to VM like item id etc. But on iOS, this is not possible. If I try to initialize ViewModel inside init block of my View, it doesn't compile saying get-only value can't be assigned. I'm not very familiar with swift and might be doing something wrong, if thats the case some guidance would be appreciated

Using stateIn with viewModelScope.coroutineScope does not update on iOS

Hi, I've tried to add the KMM-ViewModel to my project, however I've faced one big issue. I've prepared a branch that presents this issue: AKJAW/Timi-Multiplatform@cb8888f

Here's the code for the ViewModel:

import com.rickclephas.kmm.viewmodel.stateIn
import kotlinx.coroutines.flow.stateIn as normalStateIn

class TestViewModel : KMMViewModel() {

    val intViewModelScopeFlow: StateFlow<Int> = createFlow()
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), 0)

    val intCoroutinesCopeFlow: StateFlow<Int> = createFlow()
        .normalStateIn(viewModelScope.coroutineScope, SharingStarted.WhileSubscribed(), 0)

    private fun createFlow() = flow {
        var i = 1
        while (true) {
            delay(1000)
            emit(i++)
        }
    }
}

From the source code it seems that com.rickclephas.kmm.viewmodel.stateIn uses the equivalent of what I've used

/**
 * @see kotlinx.coroutines.flow.stateIn
 */
@Suppress("NOTHING_TO_INLINE")
public actual inline fun <T> Flow<T>.stateIn(
    viewModelScope: ViewModelScope,
    started: SharingStarted,
    initialValue: T
): StateFlow<T> = stateIn(viewModelScope.coroutineScope, started, initialValue)

iOS

But the StateFlow which uses the normalStateIn is not correctly updated on iOS:

struct TaskListScreen: View {
    
    @StateViewModel var testViewModel = TestViewModel()
    
    var body: some View {
        NavigationView {
            VStack {
                Text("ViewModelScope \(testViewModel.intViewModelScopeFlowNativeValue)")
                Text("CoroutinesScope \(testViewModel.intCoroutinesCopeFlowNativeValue)")
            }

        }
    }
}

Screenshot 2022-12-22 at 20 35 04

Android

On Android both of the StateFlows behave in the same way:

Column {
    val viewModel = viewModel<TestViewModel>()
    val vm = viewModel.intViewModelScopeFlow.collectAsState().value
    Text("ViewModelScope $vm")
    val normal = viewModel.intCoroutinesCopeFlow.collectAsState().value
    Text("CoroutinesScope $normal")
}

Screenshot 2022-12-22 at 20 30 36

Expected result

The expected result would be that both com.rickclephas.kmm.viewmodel.stateIn and kotlinx.coroutines.flow.stateIn behave the same on iOS, which is currently not the case. Having to pass KMMViewModel.viewModelScope around everytime a StateFlow is created is problematic, because code will be tied to this library even though it doesn't need to be. It will also make testing code harder because as far as I see Faking ViewModelScope might not be so easy (I might be wrong). Still, I think it's better for the library API surface be limited to only the ViewModel and allow the flexibility of CoroutineScope.

Edit:
Played around with tests using the ViewModel scope, and it is not a pleasant experience :D You're forced to use Dispathers.setMain otherwise the test will always fail because we cannot change the dispatcher without using viewModel.coroutineScope + Dispatcher, however this change in production code will break iOS.
AKJAW/Timi-Multiplatform@9cf040b#diff-52ecb25b704f2601f1256c922a3fc5b45569daaf8ea3b9d51434907cdbbe7967

Additional thing is that if we need to inject the ViewModelScope, we need to create an object / class which inherits KmmViewModel and pass in the viewModelScope field. You cannot create your own implementation of ViewModelScope, because the coroutineScope extention function is tied to the implementation detail of ViewModelScopeImpl, and will throw an exception if a different ViewModelScope implementation is used
AKJAW/Timi-Multiplatform@9cf040b#diff-7c5b828548356898f03b363ec2ede9591723cb5791cab7c7a761dae664dd3170

Using environmentViewModel(_ viewModel) Causes App To Crash

When using the environmentViewModel(_ viewModel: KMMViewModel) function, the app crashes:

    ContentView()
        .environmentViewModel(viewModel)

Interesting, using the alternative environmentViewModel(_ projectedValue: ObservableViewModel<KMMViewModel>.Projection) works:

    ContentView()
        .environmentViewModel(_viewModel.projectedValue)

Great library by the way!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.