Inject everything - ViewHolder and Dagger 2 (with Multibinding and AutoFactory example)
The main purpose of Dependency Injection pattern which is implemented by Dagger 2 is that DI separates the creation of a client’s dependencies from the client’s behavior. In practice it means that all calls of
newInstance() and others shouldn’t be invoked in places other than Dagger’s Modules.
The price of Dagger-everything
The purpose of this post is to show what we can do, not what we should do. That’s why it’s important to know what we give in return for separating creation from behavior. If you want to use Dagger 2 for almost everything in your project you will quickly see that big piece of 64k methods count limit is used by generated code for injections.
Inject everything example
Probably the best known code example of Dagger 2 shows how to inject simple object (e.g. Presenter) to Activity and looks similar to this:
When we start extending this code (let’s say we work on Activity which shows list of items) sometimes we take a shortcuts. It means that we don’t always @Inject new objects (like adapter in this case):
And with this approach we act against DI and profits given by it. Calling Adapter constructor inside Activity class means that every time when we need to change something in its implementation (new constructor argument, maybe completely new implementation which shows grid instead of list), we have to update Activity code.
Sometimes taking a shortcut like this is intended (we know that this code won’t be extended more in the future). But today we’ll try to make all adapter related code a part of dependency injection approach. As an example we’ll extend our GithubClient app - its screen which shows list of repositories for given username.
To make it more complex and closer to real apps our Adapter will have three different view types: normal, extended and featured.
Here you can see starting code base for our example. For me this is how the first iteration of implemented Adapter looks like in most cases.
At the beginning we’ll inject our adapter object instead of calling its constructor in Activity class:
To make this possible we have to initialize
RepositoriesListAdapter object in our Activity Module:
Pretty straightforward. Now let’s do some refactoring of our
RepositoriesListAdapter class. Inner static classes for ViewHolders should be moved out of Adapter code:
To do it fast in Android Studio just put cursor on class name and click F6 (Move Inner to Upper Level)
Assisted injection, Auto-factory
In the next step of our refactoring process we would like to move out construction process from
It’s not as straightforward as in previous example. As you can see our ViewHolders have mandatory argument which is View object (
RecyclerView.ViewHolder class has only one constructor:
public ViewHolder(View itemView)). It means that every time when we want to create new object, we need to provide view (which is created during runtime so we cannot configure this in advance in Module classes).
Final solution (until there is no official way of doing this in Dagger 2 framework) is to inject Factory object which takes those dependencies as an arguments and creates intended object. In mentioned StackOverflow thread you can see how to do this by hand, but also Google provides us a solution to generate those factories automatically.
AutoFactory generates factories that can be used on their own or with JSR-330-compatible dependency injectors from a simple annotation. To use it in our project we have to add new dependency in build.gradle file:
Now all we have to do is to annotate classes for which we would like to create factories:
@AutoFactory annotation can make our Factory class extending given class or implementing given interface. In our case it will be:
After this update Adapter’s
onCreateViewHolder() method looks like this:
Now our code is a bit cleaner but still we call constructors inside it.
Final step in our refactoring is to initialize our Factory objects in Module class to get rid of constructors calls in Adapter class. We can simply inject them as a
RepositoriesListAdapter constructor parameters. But it would mean that every time when we decide to add/remove new type of ViewHolder we still would need to update Adapter code by hand.
Instead we can make use from Multibinding. Thanks to this feature Dagger allows us to bind several objects into a collection (even when the objects are bound in different modules).
Our ViewHolder factories have one thing in common: interface
RepositoriesListViewHolderFactory. It means that we can inject map of Integers (type of Repository object is represented as a
static final int) and RepositoriesListViewHolderFactory:
And here you can see how our map is created:
@IntoMap annotation makes those objects a part of map and puts them under keys given in
@IntKey (here you can find more annotations for different types of keys):
Finally, after all of those code improvements
onCreateViewHolder() method is as simple as possible:
No constructors, no if-else statements. Everything is done by proper Dagger graph configuration.
And that’s all - our adapter and its ViewHolders are now a part of Dagger 2 graph. We are another step closer to inject everything in our code. With AutoFactory and Multibinding it will be much simpler.
Thank you for reading! 🙂
Complete source code
Here you can find source code of described classes after refactoring:
Full source code of described project GithubClient is available on Github repository.