Web Analytics Made
Easy - StatCounter

Menu

About us Contact us

Dagger for Android: A Beginner’s Guide to Multibindings

Tech 2019 / 09 / 23

Dagger for Android: A Beginner’s Guide to Multibindings



The happy marriage between ViewModeland Dagger using @IntoMap

・・・

 

One particular challenge we face when working with ViewModelarchitecture component together with Dagger is that there’s no straightforward way to provide a  ViewModel dependency having non-zero argument constructor that will be conscious of the lifecycle associated with the injection target such as activity or fragment. One solution to this problem is to implement a ViewModelProvider.Factorywhich can be used to instantiate  ViewModels in a lifecycle-aware way. This implementation of ViewModelProvider.Factoryneeds a Mapdependency that Dagger has to satisfy. The way we achieve this resolution is by using multibindings.

Multibinding is a way of binding several objects of some given type into a collection that application code can inject without depending on the individual bindings directly. To make things easy, Dagger comes with a few annotations such as @IntoSet, @ElementsIntoSetand @IntoMapfor different use cases.

Note: henceforth in this post, I’m going to write Factoryinstead of  ViewModelProvider.Factory to keep it short and easier to read.

The map multibinding is especially useful if we want to create a Factory instance. We have to bind all the  ViewModels into a map using the  @IntoMap annotation. Dagger will manage the map itself and provide as the dependency of Factory implementation when we tell it to. We’ll see how it can be done in the examples of this article. But before that, let’s take a step back and see why exactly we need this.

Note: If you want to learn about  @IntoSet and @ElementsIntoSet, you can start from here.

Why @IntoMap?

There are a few ways you can create a  ViewModel instance. Since it’s a lifecycle-aware component, it needs a reference of the lifecycle owner such as an activity or fragment. You can instantiate a  ViewModel object from an activity or fragment in the following manner to make it lifecycle-conscious:

val viewModel = ViewModelProviders.of(this)[YourViewModel::class.java]

Sweet! But there’s a catch — YourViewModelclass cannot have any parameter in its constructor. But, this is impractical, in real-life scenarios, it can have any number of parameters such as repository, or if you’re into the clean architecture, one or more use cases. For non-zero argument constructors, you can instantiate the object in the age-old way:

val viewModel = YourViewModel(argA, argB)

Can you see the problem here? The  ViewModel isn’t aware of the lifecycle owner in this approach and therefore it will not survive activity recreation. This is basically the MVP pattern where  ViewModel is taking the presenter’s job. This is where the  Factory interface comes into play. You can implement it and override the create()function to instantiate your  ViewModel (we’ll come to the details in a while) and write something like the following in your activity or fragment class:

val viewModel = ViewModelProviders.of(this, FactoryImplementation())[YourViewModel::class.java]

Instead of creating a  Factory instance every time, you can make it a singleton that Dagger will provide to our injection targets, i.e., activities and fragments. Let’s see how.

But before starting off, I would recommend you clone the repository and checkout part-3branch. All the examples given in this post are from this project. If you’ve been following this series, you should know by now that I created the project based on dagger-androidand the latest version of Dagger as of September 2019 (v2.24) so that you can write the code described in the article and learn Dagger interactively. The stuff necessary for this part was already implemented in the previous post and the complete source code can be found in the aforementioned branch. If this is your first time here, you can check out part 1 and part 2 where I covered initial setup for Dagger, providing dependencies and scoped bindings. This post is written on the assumption that you already have the basic idea about these things.

Implementing ViewModelProvider.Factory

Create a class that implements ViewModeProvider.Factoryand overrides  create() function. Add a Map<Class<out ViewModel>, Provider<ViewModel>>parameter to its constructor as the dependency. For example, take a look inside the ViewModelFactory.ktfile which contains all the implementation details. Since we want to instantiate the class only once in our app’s lifetime, annotate it with the same scope as our AppComponent’s — @Singleton:

Now it’s time to tell Dagger how to resolve this dependency by providing the map.

Creating the map key

Create an annotationclass that Dagger will use to create the key of the map. For example, take a look inside  ViewModelFactory.kt which is used to tell Dagger that the key of the map will be a ViewModel Classinstance:

Binding into map

Create an abstract @Moduleclass and write an abstract function inside it to bind your  ViewModel into the map using @ViewModelKey. The return type of the function tells Dagger that the value of the map will be a Provider<ViewModel>instance and the key will be the  Class instance of your  ViewModel. Take a look inside the   ViewModelFactory.kt file to see it in action:

In your own projects, write an abstract function for each of your  ViewModels in this fashion. Make sure the  ViewModel implementation has an @Inject-annotated constructor so that Dagger knows how to provide the instance.

Since the binding of MainViewModelis now done inside a module installed into @Singleton-scoped AppComponent, we have to disassociate MainViewModelfrom @ActivityScopeby removing the scope annotation.

We want to inject our MainActivityand CatFragmentwith the Factory instance. So we need to bind Factory to its implementation — ViewModelFactory:

Add the module to AppComponent:

Injecting MainActivity and CatFragment

Now replace the  mainViewModel property with  viewModelFactory of type ViewModelProvider.Factoryin both MainActivityand CatFragment:

@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory

In onCreate()function of  MainActivity, instantiate  MainViewModel using this statement:

And in onViewCreated()of CatFragment:

Now run the app. If you rotate the screen now, the activity will be recreated, but it will get the same  ViewModel instance and the list already fetched from the server will be dispatched by the LiveDatawithout any need for additional network call.

In case you haven’t noticed, now the CatFragment subcomponent doesn’t have any dependency on any of its parent component’s scoped bindings. Therefore, it doesn’t need to be a child of MainActivity component anymore. As long as all the activities and fragments in your projects depend only on Factoryor any other @Singleton-scoped object, the subcomponent can be a child of AppComponent. That means, MainFragmentBindingModuleand ActivityBindingModulecan be installed independently into AppComponent. In other words, CatFragmentand MainActivitysubcomponents can be mutually independent sibling components. In this way, you can bind as many fragment subcomponents as you like inside the MainFragmentBindingModule. Since it’s no longer installed into the MainActivitysubcomponent, you can remove the prefix ‘Main-’ from its name and simply call it FragmentBindingModulethat would contain all the fragment subcomponent bindings of your project.

You can find the complete source code of this article in the branch part-3-complete:

AVI5HEK/Purrfect-App

You can also find part 1 of the series here:

Dagger for Android: A Beginners Guide — Part 1

And part 2 here:

Dagger for Android: A Beginner’s Guide — Part 2

Please do share your thoughts. You can reach out to me on Twitter: @Avishek_Khan. You might also want to check out more tech posts on our blog. Until next time, happy learning!

You have ideas, We have solutions.

CONTACT US!