Hi guys. I use Koin in Compose for the DI. For one...
# compose
h
Hi guys. I use Koin in Compose for the DI. For one of my ViewModels, I created a custom scope. I start and close that scope manually. When I close that scope, the ViewModel onCleared is not being called. Does it mean that the ViewModel somehow stays active under the hood? Can't find any good documentation regarding this.
Copy code
// That's in the module
scope(named(LOGGED_IN_SCOPE)) {
    scopedOf(::MyViewModel)
}
// When I want to create my scope, I call this
Copy code
fun createLoggedInScope() {
    val koin = KoinPlatformTools.defaultContext().getOrNull() ?: return
    _loggedInScope.update {
        koin.getOrCreateScope(LOGGED_IN_SCOPE_ID, named(LOGGED_IN_SCOPE))
    }
}

fun closeLoggedInScope() {
    _loggedInScope.value?.close()
    _loggedInScope.value = null
}
And whenever I use my ViewModel, I do
Copy code
val myViewModel by loggedInScope.inject<MyViewModel>()
c
Well firstly, there isn't really a point to creating a singleton viewmodel. When using koin with ViewModels you should be leveraging the koin api to provide a factory that lets the ViewModelStoreOwner (most likely fragment/activity) create and manage the instance of your ViewModel. There are examples in the docs. It is the lifecycle calls of the component containing the ViewModelStoreOwner that in turn calls the onCleared method for all the ViewModels in their ViewModelStore. Since you are creating this viewmodel manually, you are opting out of leveraging the managed viewmodel, and its expected onCleared doesn't get called. You should probably just use a regular class, create your scope that is tied to the user's auth state, and provide said class as a singleton in the created scope. Be careful about accessing state that is held in the classes within the scope, after the scope is closed. On a side note, you probably shouldn't be using scopes this way. Instead of passing the scope down your compose tree and creating the viewmodel somewhere down this tree, hoist the viewmodel to the top level composable or fragment, and pass callbacks that reference your viewmodel's functions to your composables. Another possible issue, what happens when you close this scope and a callback or effect in your composable attempts to call one of its methods?
h
Thank you. Let me make some clarifications. I use the compose navigation library, that manages the ViewModelStoreOwner differently. I.e., when I have something like this in my module -
viewModelOf(::MyViewModel)
- and I pass it in a constructor of my composable, the ViewModel gets cleared when that composable is disposed or a configuration change happens. So I guess it's tied to my Composable lifecycle, not activity or fragment. For my purpose I can use the
viewModelOf
in my module, it's just gets cleared and reinit on configuration change, i.e., new instance is created each time. If I could fix that, I wouldn't need custom scopes too. Regarding other issues, I can check whether my scope is closed or not before performing any operation. Though yeah, I agree that my approach is not that good, but it's just a template code, cause in the real app everything is way more complexed.
c
When using Jetpack Navigation with composables, the viewmodel you create will be tied to the value returned from LocalViewModelStoreOwner.current. This in turn defaults to the closest ViewModelStoreOwner, which in the case of compose navigation would likely be the composeable(route = ....) you defined in your NavGraph. This viewmodel will be the same instance when accessed in child composables even if the child composable is removed from the composition and added again. It will also survive configuration changes, so something else is configured incorrectly. Therefore the lifecycle of the viewmodel is deteremined by the place it was instantiated, or the value passed in for ViewModelStoreOwner in koin or jetpack's apis. Or an honorable mention for the case of no value being passed for ViewModelStoreOwner on instantiation, but the composition local for LocalViewModelStoreOwner is provided. Overall, you have the flexibility to scope to an activity, fragment, navgraph, backstackentry/destination, plus any ViewModelStoreOwner I may be missing. I'd see which of these suites your needs.
❤️ 1
For the configuration changes, I think it may be resolved if you inject with koinViewModel() instead of inject...(...) ,
viewModel: MyViewModel = koinViewModel()
. I think you are just creating an instance of the class with inject...(...), but not providing a factory and adding it to the store.
h
Yeah, I see. I noticed too that it's weird for the ViewModel to init again after a configuration change, it shouldn't happen actually. I found out that I had a navigation issue when the configuration change happened, for a moment my navigation flickered, i.e., the initial screen was navigated for just a moment and returned back, that's why the ViewModel was created again because the navigation slightly changed. After fixing it now my ViewModel survives configuration change. No scope is needed at all. Thank you Caleb.
Is there any difference between koinViewModel and getViewModel or they are just the same thing under the hood?
102 Views