Closed pyus13 closed 4 years ago
Currently you can use Multibinding to create a generic view model factory, it's less than ideal, but it means you don't have to create or update your view model factory.
https://gist.github.com/ashdavies/2a96facbbe766f404d1b770182255da9
A good example I found was the googlesamples/android-architecture-components GitHub example ViewModelFactory. And the Injector object they use makes it easy to remove a lot of the boilerplate.
The example does force you to use Unscope/Singleton dependencies for ViewModel
s but with the ViewModel
cache this is a little moot since the factory will not get called, and the context should hit the ViewModel
store cache. You can scope the factory, but, to get it all started this works well enough.
As workaround I developed a lib, which generates Factory
classes for ViewModels
.
Usage example:
But it doesn't help with tests.
So far I've found this to be the best solution:
class InjectableViewModelFactory<VM> @Inject constructor(private val viewModel: Lazy<VM>)
: ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return viewModel.get() as T
}
}
With that, you just add your ViewModel to Dagger however you want (with @Inject
constructor, with module etc.) and then Inject InjectableViewModelFactory<YourViewModelType>
into your Activities/Fragments. It retains scope and everything.
For tests, you just instantiate your mocked ViewModel manually and create instance of this class manually: viewModelFactory = InjectableViewModelFactory(Lazy { viewModel })
@matejdro I've started using a similar approach, this is also similar on the Plaid app, though I think it better to use Provider<VM>
instead of Lazy<VM>
.
From what I see, Plaid app has separate factory class for every single ViewModel? So it is not the same approach.
I'm not sure if Provider vs Lazy makes a difference, factory is not supposed to be called multiple times, right?
Similar, not the same. Yes, the activity ViewModelStore
will manage the lifecycle of the ViewModel
, thus will only call get()
when it is necessary, so Provider
is only required to know how to generate the instance, whereas Lazy
will store a reference to be re-used, and often uses a synchronised thread-safe algorithm.
I think the solution that we come up with for #1183 should be applicable here (i.e. allow @ContributesAndroidInjector
to work for view models)?
Personally I do not think @ContributesAndroidInjector
is a good idea, since it forces you to use field injection. ViewModel is not initialized by framework without a way to intercept like activities, so you can just use constructor injection.
Agreed that field injection shouldn't be used when constructor injection is available, it seems that the @AssistedInject
presented by @JakeWharton at last years Droidcon UK is the preferred option by many
So far I've found this to be the best solution:
class InjectableViewModelFactory<VM> @Inject constructor(private val viewModel: Lazy<VM>) : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { @Suppress("UNCHECKED_CAST") return viewModel.get() as T } }
With that, you just add your ViewModel to Dagger however you want (with
@Inject
constructor, with module etc.) and then InjectInjectableViewModelFactory<YourViewModelType>
into your Activities/Fragments. It retains scope and everything.For tests, you just instantiate your mocked ViewModel manually and create instance of this class manually:
viewModelFactory = InjectableViewModelFactory(Lazy { viewModel })
I'm not able to use your suggestion. Is it still valid with the latest dagger? I keep getting an error about "Not able to provide ViewModel without @Provides annotation" even though it has an @Inject constructor and I have @ContributesAndroidInjector on the fragment... Do you have any other examples other than the WearMusicCenter application? It seems a bit outdated
It still works normally in latest dagger. You likely have some other error. I would move that discussion to stack overflow though.
@matejdro Thanks for the reply. I created a stack overflow post, have a look if you have time. https://stackoverflow.com/questions/59381817/injecting-viewmodel-into-the-fragment-with-dagger-2-with-parameters
I'm implementing something similar and I want to make sure I understand the OP's problem (and that I don't have the same problem w/out yet knowing it ;) )....
The OPs issue comes from wanting a single Factory for all of their ViewModels. If instead they created a separate Factory for each VM then they could do what Plaid does: example: use constructor injection on factory and then pass dependencies right into VM constructor, they they would not have this problem, right?
Side note @pyus-13 where did you read "AndroidViewModels are not supposed to be created by calling constructors" ? I can't seem to find that anywhere. I thought that is exactly what the Factory was for... calling VM constructors.
Google, where is the official solution? This is an architectural problem between two fully-supported Google libraries that has been known for at least two years.
Am I missing something? Is this on the roadmap?
Similar to https://github.com/google/dagger/issues/1271, since Hilt has been released along with the Jetpack extension for ViewModels, that is going to be the official ViewModel injection solution. dagger.android is unlikely to get an update to support ViewModels, so I'm going to close this.
This is what @Chang-Eric is referring to: https://developer.android.com/training/dependency-injection/hilt-jetpack#viewmodels
Sadly I wish there was a "vanilla" Dagger (aka w/out dagger-android or Hilt) way of doing this. I really don't want to use Hilt just for this one thing.
It looks like AndroidInjection only allows to inject dependencies in Android main components and Fragment. With the new MVVM pattern my most of the application logic lies in AndroidViewModel classes and I want to inject dependencies there.
One way is to write a custom factory and pass the dependencies for every single ViewModel in the app.
It is too much work and a lot of boilerplate code. Also, it forces me to update the factory whenever I need to pass new dependencies in my ViewModel. Then I need to update my tests for ViewModels.
Cant dagger ship anything which makes dependencies injection in AndroidViewModel classes possible. AndroidViewModels are not supposed to be created by calling constructors so DI if the perfect solution for it.