Practical guide to Dagger 2
This was originally published at my personal blog.
There are plenty of guides around about using Dagger for Dependency Injection on Android. They explain what it is, why it is needed, but miss on explaining how to practically do it. Practically using dagger had eluded me for a long time.
This article will cover how to use dagger 2 in a simple situation. If you are looking for advanced usage with scopes and other stuff, this isn’t what you are looking for.
Let’s get started!
Understand the problem
Before understanding the problem, lets see where this problem comes from. We want to implement a concept called Dependency Injection. It says that if a component depends on another component, instead of making this dependency itself, it should be provided (injected) from the outside. This will help us in loose coupling between the components, a simple way to test the behavior of a component and much cleaner code. A basic example code is:
Here the class A has a dependency on class B. B is supplied to its constructor. This is dependency injection. Let’s take an example from the android world.
Suppose you have implemented MVP or MVVM pattern. Your presenter (or ViewModel) depends on a database which requires (Application) context in it’s constructor. Since you shouldn’t send android specific stuff to presenter, your view, the activity will end up instantiating the database. The code looks like this:
Now, that seems wrong. Why would the view care about the database. Wouldn’t it be better if presenter took care of that itself? This is our problem — that the view knew about something it shouldn’t, making the involved components tightly coupled.
This is one of the good reasons to use Dagger 2 for dependency injection. If the dependency was injected to the presenter without view’s intervention, the code becomes more modular and loosely connected which helps in easily changing one component without affecting another.
Now that you can see where the problem lies, let’s try to understand how to use dagger, and then we will solve this problem.
First we need to include dagger in build.gradle. This is configuration I have used as of July 2018 (Replace the version 2.16 with whatever is the latest version).
To include in an android module, add this in the module’s build.gradle :
And for a java module, add this in it’s build.gradle
// this is for using apt for annotation processor
id "net.ltgt.apt" version "0.10"
Dagger works via annotations to understand what you want to inject and what the dependencies of a component are. Some basic annotations are:
This annotation is used in 2 ways:
1. By a component to declare it wants these dependencies
2. Telling dagger to use this constructor to make the object if you want to inject this as a dependency. This injection is recursive, i.e. if the constructor has parameters, dagger will look for ways to inject them as well.
Let’s take an example to clear that statement. MyClass asks dagger to inject A (Usage 1). Dagger tries to inject A via it’s constructor call (Usage 2) that leads to B’s constructor (Recursive injection — Usage 2). Note that for this to happen, we have marked B’s constructor with @Inject annotation. This will happen until the dependency has a no parameter constructor or until it was already present with dagger somehow (Somebody else created it before you).
- @Provides and @Module
Now you are wondering, what about cases where there are no constructor, like Retrofit client as it is built by its builder. Or when we can’t instantiate the dependency, like a context object. We can’t use @Inject for such cases. Dagger has a solution for such case.
There are two annotations for such cases. First is @Provides. Marking a method with this annotation tells dagger, that this method provides the return data type. Let’s take an example to clear that hard statement. Suppose we have a function called getContext() which returns Context object. If I mark this method with @Provides, dagger now knows where to find the context (Since it’s the return type of our method).
But wait, what if I need to inject ApplicationContext and ActivityContext as dependency somewhere. For both the return type would be Context. Well, dagger provides a @Named annotation to mark an Id for your @Provides methods. So for application context we would use something like @Named(“ApplicationContext”) and similarly for ActivityContext. You can find the example in the code below.
But where do I define this @Provides methods?
The answer is @Module. It is used on a class to grouping of similar types of @Provides methods together. For example a class named ContextModule marked with @Module will have @Provides methods for ApplicationContext and ActivityContext. These methods will use @Named to differentiate between the same return type. Also, remember that you need to specify the name or id when asking for your dependency if you have used it when we provide the dependency. Example: Context is marked with name “ApplicationContext” in MyDatabase code below. In this way dagger knows to inject the dependency marked with this name.
As you can see, the module asks for the context it has to provide in it’s constructor. If there is any component like Context or ContentResolver that we can’t instantiate or even build like Retrofit, we ask for them in the module’s constructor. And then via it’s @Provides methods the module will tell Dagger what it can provide.
- @ Component
We have seen the ways we can ask and provide dependencies. Now we need a way to define the component which connects those annotations i.e. will inject those dependencies wherever needed. So logically, we need to tell it the modules to use and the consumers, the one’s who needs these dependencies. @Component automatically picks up @Inject constructor calls. We’ll see the code in the coming examples.
Solving the problem
Remember our problem — the view was instantiating the database and then passing it to the presenter.
First we look at the MyDatabase class. We already saw how we marked it’s constructor as @Inject. This tells Dagger to use this constructor to inject this whenever MyDatabase is needed as a dependency. It also tells dagger that my constructor parameter that I depend on, is named “ApplicationContext”. Then we used a ContextModule to provide that “ApplicationContext” named dependency.
Now, we will define the component. We need to tell it what modules to use, and where it would need to inject. Since we need to inject the MyDatabase to MyPresenter, we only need to tell Dagger about the ContextModule (Since component will pick up @Inject on MyDatabase constructor directly). To tell it where we need to inject, we define a function called inject that returns void and takes the consumer as a parameter. Example:
But this is just an interface. Where is the code that will actually inject?
Build the project once. Dagger generates all the code that is required to inject dependencies at compile time. After the build, DaggerMyComponent class is created which implements the MyComponent interface. Dagger generates this implementation with a Dagger prefix in the name. This implementation has methods to provide dependencies and inject them to our consumer (Both of which we defined in the interface).
Now we need a way to provide this component to the consumers. In most cases the dependencies we want to inject can be created on an application scope, hence we declare the component as a static variable in the Application class. Next we need to build it using the builder function in the DaggerMyComponent class. The builder of the components only asks for the modules it depends on. Finally, we make a function to return the component.
Now here is the presenter asking for dependencies to be injected.
That’s it. We have declared where we would provide our dependencies, and how consumers can ask for them.
Bonus 1 — Injecting interfaces instead of implementations
There is one more thing you should know. Suppose MyDatabase was implementing an interface. Let’s call it Database. Instead of directly injecting the implementation, we want to inject the interface instead. For this, first we need to make a module which takes an implementation as it’s constructor parameter. Then in the ‘provides’ method, it returns the implementation object(MyDatabase), but the return type is of the interface (Database). Thus effectively when we inject the interface, it will be a reference to an actual implementation. Then we need to include this module in our component, and inject the interface directly.
Bonus 2 — Singleton
Yeah, Singleton. We all have been in situations where we needed to make a dependency a singleton class, e.g. Database, or maybe a state manager of some sorts etc. There are two rules:
- If you are injecting the dependency via @Inject on the constructor of the dependency, just mark the class as @Singleton.
- If you are using @Provides in a module, just mark the method as @Singleton.
Dagger will make sure that for the given scope, that is application scope in our simple example, the dependency remains a Singleton.
In summary, the rules for using Dagger for Dependency Injection are
- Use @Inject on constructors wherever you can.
- Make modules for stuff you can’t instantiate directly (e.g. Retrofit client as it’s built by it’s builder) or for stuff you can’t create like Context.
- Also make ‘provides’ method in modules if you want to inject interfaces instead of implementations.
- Use @Singleton if you want the dependencies to remain a Singleton.
- Include all the required modules in the component and define where that component will inject dependencies to.
- Use @Inject to ask dagger to inject dependencies you have declared.
- Introduction to Dagger 2, Using Dependency Injection in Android: Part 1
- Introduction to Dagger 2, Using Dependency Injection in Android: Part 2
- Android Dagger2: Critical things to know before you implement
- Tasting Dagger 2 on Android
- Snorkeling with Dagger 2
- Inject everything — ViewHolder and Dagger 2 (with Multibinding and AutoFactory example)
I hope this article helped you.