Hey, I'm using koin in an android compose app and ...
# koin
f
Hey, I'm using koin in an android compose app and pass some parameters to some view model. I assumed that the view model will be recreated every time the parameters change because the remember uses
parameters
as key in
ViewModelComposeExt
:
Copy code
return remember(qualifier, parameters) {
        scope.getViewModel(qualifier, { owner }, parameters)
    }
But actually it isn't like that. It returns the same view model with old parameters. Is that behavior intended? Am I missing something?
👍 1
c
I may be mistaken, but my understanding is that scope.getViewModel will return the last created ViewModel as long as it was not "cleared". I've always assumed that the parameters injected to a ViewModel cannot be changed (even after configuration change). But like I said I might be mistaken so I'm interested in knowing the answer to this as well.
l
Is
remember
something from Koin?
Normally a
scope
only returns a new instance when you close and recreate a scope
As long as your ViewModel is also defined as `scoped`:
Copy code
scope(named("myScope")) {
        scoped<ViewModel> {
            ViewModel( /* constructor params */ )
        }
     }
scoped
for the ViewModel means here "A singleton with the lifetime attached to the scope"
Which means to create a new instance of ViewModel you have to close the scope:
Copy code
getKoin().getOrCreateScope(
    scopeId = "myScope",
    qualifier = named("myScope")
).close()
And then next time you try to access the ViewModel through the same scope it will be a new instance (valid untli you close the scope again):
Copy code
getKoin().getOrCreateScope(
    scopeId = "myScope",
    qualifier = named("myScope")
).get<ViewModel>()
(of course it makes sense to create extension functions/properties for
getOrCreateScope(...)
to not have to type it again and again)
c
remember
is from Compose. The lambda passed to
remember
is re-executed when any of the keys change. In the original example above,
scope.getViewModel
will be called any time
qualifier
or
parameters
changes. However, it will not result in a new instance of the ViewModel being returned because like @Lukasz Kalnik mentioned Koin returns the same instance that it created for the active scope. Disclaimer: This is just my understanding and I could be totally wrong. Would be nice to get a more authoritative answer.
👍 1
l
Isn't the cause of the problem having the dependency injection inside of the view layer (Compose)?
The point of the ViewModel is that it remembers the values across configuration changes, so I don't see a benefit from wrapping a ViewModel in the
remember
Also I normally don't scope a ViewModel (view layer), but the layers below - data sources, repositories etc.
This lets me having the ViewModels depend on the instance of e.g. a Retrofit API which then is scoped to the currently active baseURL (which can change, invalidating the scope). Fabian, probably it would help to know your specific use case to think about how to solve the problem in a more architectural way.
c
In my opinion the problem is expecting a different instance of the ViewModel when the injected parameters change. The way I see injection parameters for ViewModel is: • The supplied parameter is injected into the constructor the first time the ViewModel is being created • If you want to change something in the ViewModel beyond that then it should be a mutable property (or use a function or whatever), but it should not be a constructor parameter Having DI inside the View layer is IMO fine. As long as only a top-level "Screen" composable uses it and not lower level ones. In this respect, a top-level composable plays the same role as a Fragment or Activity would in a multi-fragment or multi-activity app (and you would inject a ViewModel into the Fragment or Activity in that case)
f
Yes we use injected view models in top level composables. But in the current use case we have some kind of container composable where we inject a view model. That view model is responsible for a general network request. And in the container itself is another screen-like composable where we inject another view model. We use some state hoisting to pass data coming from the general network request to the inner composable. The inner view model needs to request more specific data depending on the general data coming from the outer view model cc @Lukasz Kalnik
l
Why do you need
remember
then? And when is the
scope
closed?
f
The
remember
is used inside the koin compose extension: https://github.com/InsertKoinIO/koin-compose/blob/main/koin-androidx-compose/src/main/java/org/koin/androidx/compose/ViewModelComposeExt.kt#L54 We dont use a custom scope for now. I'll have a look on that now
l
If you don't use a custom
scope
then you can just remove it. And inject the
ViewModel
with a
factory
. Then every time you will have a new instance.
I don't have experience with Koin in Compose unfortunately.
We just have all the dependencies defined in Koin `module`s and then access them with
by inject()
or
get()
. I don't know if it works well in Compose though...
f
If you don't use a custom
scope
then you can just remove it
What do you think should be removed?
l
The
scope
You wrote:
We dont use a custom scope for now. I'll have a look on that now
👍 1
But anyway, if it's just the rootScope, I guess it doesn't change anything so you might as well leave it 🤷
c
Koin has a Compose integration:
getViewModel
which you can call from a Composable.
👍 1
879 Views