rickclephas / KMP-ObservableViewModel

Library to use AndroidX/Kotlin ViewModels with SwiftUI
MIT License
569 stars 29 forks source link

XCode preview crash #63

Closed lukaszkalnik closed 4 months ago

lukaszkalnik commented 5 months ago

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
rickclephas commented 5 months ago

Could you share how you are providing the viewmodel to the SwiftUI preview?

lukaszkalnik commented 5 months ago

I don't provide it at all to the preview, it's inside the ContentView as a @StateViewModel:

struct ContentView: View {
    @StateViewModel var viewModel = MainViewModel.companion.instance // from KMP shared module

Should I also inject it in the preview somehow?

rickclephas commented 5 months ago

I am assuming that MainViewModel.companion.instance is some kind of singleton instance of MainViewModel, correct? Using singletons is a little tricky. KMM-ViewModel creates an observable wrapper for your viewmodel. However a viewmodel can only be wrapped once, so the viewmodel shouldn't outlive the wrapper.

How is the singleton instance created? Are you able to construct the MainViewModel in SwiftUI directly?:

@StateViewModel var viewModel = MainViewModel()
lukaszkalnik commented 5 months ago

The instance is created in the KMP shared code, because the viewmodel takes some dependencies (also from the shared code).

GuilhE commented 4 months ago

I don't provide it at all to the preview, it's inside the ContentView as a @StateViewModel:

struct ContentView: View {
    @StateViewModel var viewModel = MainViewModel.companion.instance // from KMP shared module

Should I also inject it in the preview somehow?

@lukaszkalnik how's your DI setup? I believe I have the same use-case but I don't face your issue:

actual fun platformModule() = module {
    factory { TimerViewModel() }
    factory(named("NoStateRestore")) { TimerViewModel(false) }

}

@Suppress("unused")
object ViewModels : KoinComponent {
    fun timerViewModel() = get<TimerViewModel>()
    fun timerViewModelWithoutStateRestore() = get<TimerViewModel>(named("NoStateRestore"))
}
struct TimerScreenInSwift: View {
    @StateViewModel private var viewModel = ViewModels().timerViewModel()
    ...
}

If you wish to take a look: sample

lukaszkalnik commented 4 months ago

Currently I don't use any DI library, but maybe that's the source of the problem. I didn't have time to get back to this project yet, but once I do, I will let you know. In the meantime I think I can close the issue. Thank you both for the hints!