rickclephas / KMP-ObservableViewModel

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

State update is reinitializing the entire screen in SwiftUI #51

Closed ghost closed 10 months ago

ghost commented 11 months ago

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.

rickclephas commented 11 months ago

I believe this is to be expected and also the case for pure Swift ObservableObjects, right?

ghost commented 11 months ago

It happens when I'm using environment variable, but not when I pass the KMM View Model using DI with constructors of the View. So, in the root view, I would create instance of view model without using @StateObject or @StateViewModel, and then in child view, I would have @StateViewModel for that view model which will be initialized inside init where I would pass the regular view model:

screenshot_2023-10-11_at_10 46 56

rickclephas commented 11 months ago

So how are you creating and storing the viewmodel when using @EnvironmentViewModel? It sounds like some higher view is also observing the viewmodel, resulting in the calls to the init.

ghost commented 10 months ago

Inside of the root App module I have:

@StateViewModel var homeViewModel: HomeViewModel = ViewModelsHelper().homeViewModel

and then:

var body: some Scene { WindowGroup { HomeScreenView() .environmentViewModel(homeViewModel) } }

rickclephas commented 10 months ago

That explains the calls to the init. Since you are observing the viewmodel with StateViewModel in App the body is being recreated when a state change happens in your viewmodel. Removing the StateViewModel property wrapper should fix that.

ghost commented 10 months ago

So then, how do I use .environmentViewModel?

rickclephas commented 10 months ago

The following should be enough to use environmentViewModel:

struct MyApp: App {

    var homeViewModel: HomeViewModel = ViewModelsHelper().homeViewModel

    var body: some Scene {
        WindowGroup {
            HomeScreenView().environmentViewModel(homeViewModel)
        }
    }
}