Codementor Events

Understanding Dagger 2 Multibindings + ViewModel

Published May 11, 2018Last updated Nov 07, 2018
Understanding Dagger 2 Multibindings + ViewModel

Also: GithubViewModelFactory explained and rewritten in Kotlin.

Originally published on blog.kotlin-academy.com by Alex Facciorusso

Hi guys,
today we are going to explain how to use the fabulous Dagger 2 multibindings with new Architecture ComponentsViewModel class.


Many of us, when started learning how to use the new ViewModel class, had a look at the GithubBrowserSample example on GitHub (this is a GitHub-ception!).

Maybe, the first class we looked in that bunch of useful code, is the so called GithubViewModelFactory class (here’s a link to the file in the repository). 
This class is a perfect example of when Dagger 2 multibindings can be real lifesavers.

Before to dive into code, I should make a little premise.
For building customViewModel classes with argument-passing constructors (e.g. for passing custom data or @Inject annotated constructors), we must provide a class that extends ViewModelProvider.Factory, returning instances of our custom ViewModels into the create() method.

So, the first thing we can notice is the constructor of the class, that takes the not very beautiful to see Map<Class<? extends ViewModel>, Provider<ViewModel>> parameter. What is this monster?

Let’s read this huge parameter type with more attention: it is a map that has a Class that extends ViewModel as key, and a Provider of ViewModel (a Dagger 2-specific class that let us to provide — and so instantiate — a dependency-injected class) as value.

Ok ok, but what can I do with this object?
Well, we just said that the create method of our custom ViewModelProvider.Factory expects an instance of the ViewModel as return value. This method takes the type of the ViewModel that was requested from an Activity or Fragment as parameter. Pairing this type (our Class object) to something that creates a ViewModel of the same type, we can, of course, instantiate and return that class to the system.

What is that “something” that can give us the ViewModel of a determinate type? The answer is theProvidervalue in the map that Dagger 2 injected us.

Let’s talk about Multibindings


The big big title on the official Multibindings documentation

Dagger 2 can associate a dependency Provider to a given key, and inject it into a Map.

This is accomplished using the @IntoMap annotation on a method that produces the value we want to associate with a given key. 
The key is, on the other hand, specified using a custom annotation that is itself annotated with @MapKey . Here it is the annotation used for creating a Map with, as key type, a Class<? extends ViewModel> object.

@Retention(RetentionPolicy.RUNTIME)
@MapKey
@interface ViewModelKey {
    Class<? extends ViewModel> value();
}

Our value parameter type will be the type of our map’s key.

So this Module method:

@Binds
    @IntoMap
    @ViewModelKey(UserViewModel.class)
    abstract ViewModel bindUserViewModel(UserViewModel 
userViewModel);

says roughly: “inject this object into a Map ( @IntoMap annotation) using UserViewModel.class as key, and a Provider that will build a UserViewModel object (the parameter of the @Binds annotation) as value”.
In this way, we can inject into a Dagger 2 managed object, a map of type… guessed: our famous Map<Class<? extends ViewModel>, Provider<ViewModel>> .

Now we know that we can use the Class object taken as parameter in the ViewModelProvider.Factory.create() method to retrieve a provider for that ViewModel. This is exactly what googlers done in that method. Let’s examine the code piece by piece.

The possible provider for the given ViewModel is getted from the Map:

Provider<? extends ViewModel> creator = creators.get(modelClass);

if our Providers map hasn’t got that specific key, we will check if there is a subclass of the ViewModel we must instantiate:

if (creator == null) {
    for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
        if (modelClass.isAssignableFrom(entry.getKey())) {
                creator = entry.getValue();
                break;
        }
    }
}

if all previous attempts of getting a valid Provider from the map will fail, we throw an exception:

if (creator == null) {
    throw new IllegalArgumentException("unknown model class " + 
modelClass);
}

finally, we can let Dagger to create our ViewModel by invoking the get() method on the Provider object, as said before, and casting it to our final type:

try {
    return (T) creator.get();
} catch (Exception e) {
    throw new RuntimeException(e);
}

We have injected successfully a ViewModel with Dagger 2!

The Kotlin translation and… a little extra!

Well, this class and its related classes are a must if you use Dagger 2 along with ViewModel, but I personally love Kotlin, so let’s convert it into this beautiful and concise language:

@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.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
                ?: throw IllegalArgumentException("unknown model class " + modelClass)

        return try {
            creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }

    }
}
@Module
abstract class ViewModelFactoryModule {
    @Binds
    internal abstract fun bindViewModelFactory(factory: GithubViewModelFactory): ViewModelProvider.Factory
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)

So if you are using Kotlin in your Android application (and if not, you really should 🙄) you can just copy and paste this code in your project or…

… or you can use the 🔥🔥 NEW AND FLAMING 🔥🔥 library I created!
You can find it at this GitHub link, along with the instructions to install and use it (see the README).


Thank you for reading this article! If you liked it, you can make me know pressing the *clap* button (and to ★ the GitHub repo to say me “thanks”) 😌. See you in comments! 😁

To be up-to-date with great news on Kotlin Academy, subscribe to the newsletter, observe Twitter and follow.

Discover and read more posts from Kotlin Academy
get started