Emil Butiri
05/09/2024, 11:15 AMViewModel
to a SwiftUI View
? KMM-ViewModel does it with the @StateViewModel
annotation, but is there a way when only using the Google libraries?Rick Clephas
05/09/2024, 11:21 AM@State
to store a regular object as a state value in your SwiftUI view.
However that only stores the object, it won’t observe state changes nor will it handle the lifecycle of the viewmodel.Emil Butiri
05/09/2024, 11:37 AMonCleared()
and coroutine cancellation. Not that I’m not content with KMM-ViewModel, for which I’m very grateful, btw 😁
So I guess the multiplatform support is meant more for use in composables.Rick Clephas
05/09/2024, 11:54 AMMarcin Piekielny
05/09/2024, 12:21 PMUIHostingController
available in the SwiftUI. In our KMP projects we extend it to handle the lifecycle of ViewModel. For this purpose we use the ViewModelStore
which is available in multiplatform same as the ViewModel
itself. When we create an instance of this controller we add given ViewModel to the store and when this instance is getting destroyed we call viewModelStore.clear()
which clears the ViewModel
and underlying CoroutineScope
attached to it.
class UIHostingControllerWrapper<V: View, VM: ViewModel> : UIHostingController<V> {
private let viewModelStore = ViewModelStore()
init(rootView: V, viewModel: VM) {
super.init(rootView: rootView)
let key = String(describing: VM.self)
viewModelStore.put(key: key, viewModel: viewModel)
}
required init?(coder aDecoder: NSCoder) {
fatalError("Not implemented")
}
deinit {
viewModelStore.clear()
}
}
Probably you can achieve something similar in SwiftUi by scoping ViewModels to navigation destinations, not views, same as Compose does. As far as I see the navigationDestination
modifier in SwiftUI has isPresented
binding. It might be a good idea to clear the ViewModel when this value is set back to false
. However I can't offer you a ready solution right now, as our iOS devs prefer to use UIKit navigation instead of SwiftUI so I didn't experiment with it yet 😅Emil Butiri
05/09/2024, 12:52 PMEmil Butiri
05/13/2024, 2:20 PMViewModelStore
with a deinit()
implementation that, like yours, simply calls clear()
on the wrapped ViewModelStore
. The View
creates a wrapper and passes its `ViewModel`s to the store. When the View
is taken off the navigation stack, it’s destroyed and the wrapper with it, calling deinit()
.
class JetpackVMStoreWrapper {
private let store = ViewModelStore()
func put<VM: ViewModel>(_ vm: VM) {
store.put(key: VM.self.description(), viewModel: vm)
}
deinit {
store.clear()
}
}
struct WhateverScreen: View {
private let vmStore = JetpackVMStoreWrapper()
private let vm = WhateverViewModel()
init() {
vmStore.put(self.vm)
}
var body: some View {
EmptyView()
}
}
Marcin Piekielny
05/13/2024, 2:26 PMView
class which handles this vmStore.put
partEmil Butiri
05/13/2024, 2:33 PMviewModel()
on the android 10 sideMarcin Piekielny
05/13/2024, 2:34 PMdeinit
works as expected for objects inside the View