https://kotlinlang.org logo
#compose
Title
# compose
d

darkmoon_uk

12/06/2020, 10:58 AM
One aspect of the way Compose handles ViewModels appears be quite troublesome... The default ViewModel factory invokes only the parameterless constructor of any ViewModel class. Clearly most ViewModels are going to need some dependencies; so parameterless construction implies that these are being provided externally i.e. by a DI framework. But what if you don't want to use a DI framework? This is not a problem if the default factory can be reassigned to one that allows you to perform your own construction, but it can't be. Yes; it's easy to create a
ViewModelProvider.Factory
with a lambda that constructs ViewModels with parameters, there is no way to assign this as the default factory. This entails that you either have to pass the factory around your composable functions to use it e.g.
by viewModels(myFactory)
or actually override the
getDefaultViewModelFactory
method. This is convenient enough in an
Activity
but becomes more troublesome if you try to do it for a
NavHost
. In other words the API design is coercing us toward using Dependency Injection frameworks in our ViewModels, rather than just plain construction. Whether you favour DI frameworks or not; construction parameters are objectively the primary mechanism in the Kotlin language for providing dependencies to an object: this at least implies that construction parameters should remain the first and best supported mechanism, followed by others. Am I missing something here or is there a case for Google to make these API's more flexible?
i

Ian Lake

12/06/2020, 4:09 PM
I'm not entirely sure I follow. The need for a Factory is driven by the fact that these are managed objects that only need to be created once - subsequent calls in the same scope won't actually call your constructor, since the ViewModel already exists
Like you said, it is easy enough to write an API that is just a ViewModel producing lambda that internally wraps a Factory, avoiding any visible Factory instances at all
1
🤔 1
d

darkmoon_uk

12/06/2020, 9:14 PM
@Ian Lake Thanks for considering this scenario. Yes the need for factories & stores (working together as a 'provider') is clear, and so is the way to create a
Factory
based on a lambda, that performs custom construction. What's unclear is how to then use that custom factory in the current framework - besides just invoking it directly. Ideally I would have this custom factory become part of the default provider, in scope of the current Activity or NavHost. But there's no way to reassign the default
ViewModelProvider
, and looking into the implementation of the default
ViewModelProvider
, it doesn't appear to offer any way to have its
Factory
reassigned/deferred e.g. one possible way might have the default provider use an ambient factory, using the ambience mechanism?
Another example of a missed opportunity here is
androidx.lifecycle.SavedStateViewModelFactory
which appears to wrap only either of the default
AndroidViewModelFactory
or
ViewModelFactory
instances, but looks like it could be extended to allow for wrapping a custom
ViewModelProvider.Factory
.
i

Ian Lake

12/06/2020, 11:47 PM
You'd use
AbstractSavedStateViewModelFactory
if you want to extend it to allow your own custom
create
calls
👍 1
I think getting hung up on what the default factory does is the wrong hill to die on. If you want a custom constructor, then you need a custom factory and you need to, at some level, pass that into your
ViewModelProvider
/
viewModel()
call. Whether you have a single
() -> YourViewModel
lambda you pass into your composable and construct a
ViewModelProvider.Factory
out of it via a helper method, whether you pass in an actual
ViewModelProvider.Factory
as a parameter to your composable, whether your composable uses an ambient as a bare bones service locator to access your custom factory, or whether you use a full blown DI system to provide either the whole factory or that wraps the construction/creation of the VIewModel up entirely for you (ala what Koin does) is all up to you
👍 1
💯 1
d

darkmoon_uk

12/07/2020, 3:33 AM
@Ian Lake Okay; passing down the Factory is reasonable. I was wondering if I was missing something about the way the default provider was supposed to work. IMO it would still be nice to see the API around the default provider become more agnostic to custom construction vs. parameter-less construction, but this works - and I can see that then invoking
viewModel(...)
with the factory does still go via the ambient store for caching purposes 👍 Thank you for the guidance.
5 Views