Guowei Lv

2 minute read

Previously, we created a centralized ViewModelFactory which can create all ViewModels in the app:

class ViewModelFactory @Inject constructor(
        private val myViewModelProvider: Provider<MyViewModel>,
        private val myViewModel2Provider: Provider<MyViewModel2>
): ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return when(modelClass) {
            MyViewModel::class.java -> myViewModelProvider.get() as T
            MyViewModel2::class.java -> myViewModel2Provider.get() as T
            else -> throw RuntimeException("unsupported ViewModel type: $modelClass")
        }
    }
}

But with more and more ViewModels being added to our app, this class will grow quickly, is there a way to mitigate this? The answer is the Multibinding feature in Dagger.

Basically Dagger can prepare a Map structure (or Set), and put it on the graph. In our case, we can let Dagger create a Map of:

  • key: the ViewModel’s class
  • value: the ViewModel itself

Then our ViewModelFactory class can be much more simplified:

class ViewModelFactory @Inject constructor(
        private val providers: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
): ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        val provider = providers[modelClass]
        return provider?.get() as T ?: throw RuntimeException("unsupported viewmodel type: $modelClass")
    }
}

Don’t forget that @JvmSuppressWildcards, otherwise it will not work.

Now we need to figure out how to create and provide this Map in Dagger.

We need a dedicated @Module for providing ViewModels.

@Module
abstract class ViewModelsModule {

    @Binds
    @IntoMap
    @ViewModelKey(MyViewModel::class)
    abstract fun myViewModel(myViewModel: MyViewModel): ViewModel

    @Binds
    @IntoMap
    @ViewModelKey(MyViewModel2::class)
    abstract fun myViewModel2(myViewModel2: MyViewModel2): ViewModel
}

This basically tells Dagger how to create this Map. The key is annotated with @ViewModelKey and the value is the return type of the functions ViewModel.

Now the last puzzle piece is to create the key annotation:

@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)

This is how we can use Dagger’s multibinding to “simplify” the implementation of our ViewModelFactory.

comments powered by Disqus