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)
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)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With