Hi all! And thanks in advance I'm struggling after...
# multiplatform
i
Hi all! And thanks in advance I'm struggling after coding a lot of time, I'm using Koin and KMMViewModel, then I've a Navigation view, I don't want to do something to crazy, but navigate... and if I do : • Push to ContentView2 • Dismiss ContentView2(So I come back to the first View) • Push to ContentView2 • Crash :
KMMViewModelCore/ObservableViewModelPublishers.swift:26: Fatal error: ObservableViewModel has been deallocated
I add video of the crash
Copy code
import SwiftUI
import KMMViewModelSwiftUI
import kmpShared

struct AppRootView: View {
    @StateViewModel var viewModel = ViewModels.shared.getHomeViewModel()

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink {
                    ContentView2()
                } label: {
                    Text("push to login")
                }

            }
        }
    }
}

struct ContentView2: View {
    @StateViewModel var viewModel = ViewModels.shared.getOnBoardingViewModel()

    var body: some View {
        ZStack {
            VStack {
                Text("Hello Content View 2")
            }
        }
    }
}
Maybe @Rick Clephas have the magic solution
r
KMM-ViewModel uses a small wrapper around your Kotlin ViewModel that allows SwiftUI to observe it. Every Kotlin VM needs a single wrapper for the lifetime of that VM. The
@...ViewModel
property wrappers will automatically create that wrapper for you. Once the property wrapper is deallocated so is the wrapper. This can result in the mentioned error when you are reusing the Kotlin VM instances. To solve this (while still being able to reuse the VMs) you should manually keep a reference to this wrapper. To get/create the wrapper you can use the
observableViewModel(for:)
function.
i
Hi! Thanks, you mean using them like this? I don't know if I understand to good how to manually keep a reference :S ( I'm an android Developer btw hehe, I feel useless in Swift xD)
Copy code
struct AppRootView: View {
    let viewModel = observableViewModel(for: ViewModels.shared.getRouterViewModel())
    

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink {
                    ContentView2()
                } label: {
                    Text("push to login")
                }
            }
        }
    }
}

struct ContentView2: View {
    let viewModel = observableViewModel(for: ViewModels.shared.getOnBoardingViewModel())

    var body: some View {
        ZStack {
            VStack {
                Text("Hello Content View 2")
            }
        }
    }
}
r
Yeah storing it in
AppRootView
will work (assuming it is never destroyed). Though in that case you want to keep the property wrapped view model in
ContentView2
. Note: since you are already reusing the VM you can use
ObservedViewModel
instead of
StateViewModel
(normally you use the state one to preserve the same VM instance for that specific view). It might also be worth asking why you are reusing the VMs. Normally when you close a view and reopen it it makes sense to also have a new VM for that newly opened view.
i
No, actually, I'd like to re create the viewmodel every time I see the screen, that was the idea no? Why is the view trying to re use the vm instead of re creating that observer? There's something I'm not getting. I mean, I go forward to the screen, the VM is created, I go back, I come again, the VM should be fresh again, how can I do that? I'm sorry, I'm feeling stupid not understanding this, and I'm sure it's super easy
I mean, I agree , trying to reuse something from a view that it's gone doesn't make too much sense in my brain
r
Alright in that case
@StateViewModel
is indeed what you are looking for. How does the
ViewModels
object in Kotlin look like?
i
Simply this (I cleaned them just to check if it was something inside generating the problem xd)
Copy code
open class SignUpViewModel() :
    KMMViewModel(), KoinComponent {
    
}
then in the koin ios part I do:
single *{* SignUpViewModel() *}*
and then the helper:
Copy code
object ViewModels : KoinComponent {
    fun getSignUpViewModel() = getKoin().get<SignUpViewModel>()
    }
and I do the helper because I was injecting something in the constructor
before it looked like this:
open class SignUpViewModel(val routeNavigator: RouteNavigator) : KMMViewModel(), RouteNavigator by routeNavigator, KoinComponent {
I shouldn't create a singleton of it, I guess it's the problem?
I want to inject things though
r
Yeah correct. The reason you are getting the same VM instances is because of the
single
call in the koin module. Try and use
factory
instead.
When using
factory
your original SwiftUI code should work as expected
i
Which makes a lot of sense, I'm new in Koin(Dagger/Hilt) user here xD
I'll dig in how to do a factory then
I guess it's not too complex?
Thanks for your time, appreciate that 🙏
👍 1
r
Yeah it works similar to the
single
function. The difference being that with
factory
koin will create a new instance instead of returning the same singleton.
i
Amazing, that's definitely going to work. Thanks! OMG this much time for this simple thing, I have to take a deeper look at Koin before messing it up like this
Thanks again a lot! Where's your "buy me a coffe"😲
r
NP happy to help (don't have a buy me coffee) 👍🏻
😊 1