How can I implement a generic ViewModel Factory to provide for all of my project ViewModels? To be clear, my ViewModels have dependencies (as constructor parameters)
1 Answers
Well, there is one called GithubBrowser and but it is not a tutorial, it is a project. You should know dagger for android to do that. Or, you may check the code below:
@Singleton
class DaggerViewModelFactory @Inject constructor(
private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
try {
@Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
This part will create a "generic" viewmodel for your entire app. In that way, the ViewModel
is created with arguments asigned. After that you need to implement the factory module in your Singleton modules, and including it on the component.
@Component(
modules = [... ViewModelModule::class]
)
interface AppCompoenent{}
Now the fun part:
@Suppress("unused")
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(MyViewModel::class)
abstract fun bindsMyViewModel(viewModel: MyViewModel): ViewModel
@Binds
abstract fun bindsViewModelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory
}
Since dagger support multibinding you are free to bind as may ViewModels
as you want.
The ViewModelKey:
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
You are basically puting values in a hashmap. These values are your ViewModel
.
Hit build! Done.After that you just inject a ViewModelProvider.Facory
in your fragment. Than in your ViewModel
you can do:
class MyViewModel @Inject constructor(
private val dependency: YourDependency
) : ViewModel() {}
To make clear what you requested on the comments. First of all, there is no specific need to know what is happening inside the DaggerViewModelFactory
, although I don't recommend learning this way because I am a strong fan of "Always knowing what's happening". FYI the DaggerViewModelFactory
is just a class that accepts a Map
with every class that extends ViewModel
as a key, and that class dependencies as a value. When using Provider<T>
Dagger knows how to find those dependencies but doesn't yet bring them to you, until you have called the provider.get()
. Think of it as just a lazy initialization.
Now check the modelClass.isAssignableFrom(it.key)
. It just checks if that class really extends ViewModel
.
As for your second question, is important to understand the first part. Since Dagger
supports multibinding, that means that you can provide dependencies using a Map<Key, Value>
. For example, a Map<HomeViewModel, Provider<ViewModel>>
will basically tell dagger that give me HomeViewModel
's dependencies. Dagger is going to say: How to know which are HomeViewModel
dependencies? And you respond: I already have defined a Key for that and it's the HomeViewModel
class itself. So you just create an annotation, combine it with @Binds
and @IntoMap
, and on the background Dagger will just perform a map.put(HomeViewModel::class, AndDependencies)
.

- 7,718
- 6
- 30
- 58
-
Should i know how to made the ModelViewFactory or with copy-paste is enought? – David Jul 24 '19 at 11:32
-
And the VIewModelKey is necessary? I don't know what are for the Target and Retention scopes. Maybe is like a Module for mapping the ViewModels? – David Jul 24 '19 at 11:47
-
1Don't worry, I will add some explorations extra on the post. – coroutineDispatcher Jul 24 '19 at 12:01
-
1done, please check again – coroutineDispatcher Jul 24 '19 at 12:12
-
1Thank you so much! Clear and easy to understand! – David Jul 24 '19 at 13:47
-
1Glad I helped :) – coroutineDispatcher Jul 24 '19 at 13:48
-
I'm just starting with Dagger in a project and is hard to understand, a lot of things i just made copy-paste without know what they does, you recommend me any way to learn it? – David Jul 24 '19 at 13:50
-
2Well this is kind of advanced when you are a beginner. I have an article on my own on dagger, if you want to learn the basics : https://medium.com/@stavro96/keep-it-simple-with-dagger-2-241d32e14de – coroutineDispatcher Jul 24 '19 at 13:58
-
Perfect i go to check it! – David Jul 24 '19 at 14:01
-
I was seeing your example in Medium but i don't understand the uses of the Scopes, the scope with Retention Annotation is only saying to run the dependency when the app is running, so have no sense made it or at least i don't see it. – David Jul 25 '19 at 07:50
-
1The scope is just a mark, nothing else. Just like override. it doesn't do anything. The only thing it does, is for dagger to know the level of the dependency. If you check the `@Singleton` annotation you will find the same. So scopes define hirearchy. Please check the video of twisted equation on scopes for better understanding. – coroutineDispatcher Jul 25 '19 at 07:55
-
Where I can find this video? – David Jul 25 '19 at 07:59
-
1At the end of my article, you have 3 resources to learn dagger. Open the twisted equations, and go to scopes serie – coroutineDispatcher Jul 25 '19 at 08:00
-
1sorry the name is Dagger 2 Android Tutorial – coroutineDispatcher Jul 25 '19 at 08:03
-
I see it and now I understand it, thanks again! – David Jul 25 '19 at 08:17
-
But there is one think I don't see clear. I know what scopes are for but for example the scope MapKey is mapping my activity so isn't just a mark for know the dependency level no? – David Jul 25 '19 at 08:23
-
1The `@MapKey` by its name doesn't look a scope – coroutineDispatcher Jul 25 '19 at 08:36
-
Last question please, to finish understand all: What's the ViewModelProvider.Factory for? – David Jul 25 '19 at 09:01
-
Since a `ViewModel` has a lifecycle on it's own, you cannot init `ViewModel` with a normal constructor, so you need a factory for it. – coroutineDispatcher Jul 25 '19 at 09:14
-
I post a new question if you can help me to fix the issue @coroutineDispatcher https://stackoverflow.com/questions/57206369/no-injector-factory-bound-for-class – David Jul 26 '19 at 08:20