· 4 min read
Dependency injection on Android: Dagger (Part 1)
On this new series I will explain what dependency injection is, what its main purpose is and how to use it on an Android project by using Dagger the best know implementation designed with Android in mind.
It will be a follow-up from my previous post about MVP for Android because I know some of you are quite interested in see them implemented in the same project, and I think they work quite well together.
This first part is going to be only a little theory to settle the basis. It’s important to understand what it is and why it exists, because if not we will think the benefits are not enough for the effort.
What is a dependency?
If we want to inject dependencies, we first need to know what a dependency is. In short, a dependency is a coupling between two modules of our code (in oriented object languages, two classes), usually because one of them uses the other to do something.
Why dependencies are dangerous?
Dependencies from high to low level are dangerous because we couple both modules in a way that if we need to change one module with another, we necessarily need to modify the code of the coupled module. That’s really bad if we want to create a testable app, because unit testing requires that when we are testing a module, it is isolated from the rest of modules in our app. To do this, we need to substitute dependencies with mocks. Imagine a code like this:
public class Module1 {
private Module2 module2;
public Module1() {
module2 = new Module2();
}
public void doSomething() {
...
module2.doSomethingElse();
...
}
}
How do we test ‘doSomething’ without testing ‘doSomethingElse’? If test fails, what method is the one that is failing? We can’t know. And things get worse if this ‘doSomethingElse’ method saves something in database or performs an API call.
Every ‘new’ we write is a hard dependency we probably need to avoid. And no, writing less modules isn’t a solution, don’t forget single responsibility principle.
How to solve it? Dependency inversion
If we can’t instantiate modules inside another module, we need to provide those modules in another way. Can you imagine how? Exactly, via constructor. That is basically what dependency inversion principle means. You shouldn’t rely on concrete module objects, only on abstractions.
Our previous example code would be something like this:
public class Module1 {
private Module2 module2;
public Module1(Module2 module2) {
this.module2 = module2;
}
public void doSomething() {
...
module2.doSomethingElse();
...
}
}
So what’s dependency injection?
You already know! It consists of passing dependencies (inject them) via constructor in order to extract the task of creating modules out from other modules. Objects are instantiated somewhere else and passed as constructor attributes when creating the current object.
But here it comes a new problem. If we can’t create modules inside modules, there must be a place where those modules are instantiated. Besides, if we need to create modules with huge constructors including lots of dependencies, code will become dirty and hard to read, with objects travelling around the inmensity of our app. That’s what a dependency injector solves.
What is a dependency injector?
We can consider it as another module in our app that is in charge of providing instances of the rest of modules and inject their dependencies. That is basically its duty. The creation of modules is localized in a single point in our app, and we have full control over it.
And finally… What is Dagger?
Dagger is a dependency injector designed for low-end devices. Most dependency injectors rely on reflection to create and inject dependencies. Reflection is awesome, but is very time consuming on low-end devices, and specially on old android versions. Dagger, however, uses a pre-compiler that creates all the classes it needs to work. That way, no reflection is needed. Dagger is less powerful than other dependency injectors, but it’s the most efficient.
Is Dagger only for testing?
Of course not! It makes easier to reuse your modules in other apps, or even change them in the same app. Think for example in an app which takes its data from some local files on debug and from an API service on release. That’s perfectly possible by injecting one module or another on each case.
Conclusion
I know this post it’s a bit hard, but I think it’s very important to establish the terms we are going to deal with on next episodes. We already know what dependencies are, what we improve with dependency inversion and how we can implement it by using a dependency injector.
On next episode we will get our hands dirty. so stay tuned!