Dependency injection with Dagger 2 - Introduction to DI
Some time ago, at Google I/O 2015 Extended in Tech Space in Cracow I had a presentation about dependency injection with Dagger 2. In a time of preparation I realized that there is a lot of things to talk about and there is no chance to cover everything in dozen of slides. But it could be a good entry point to start the new series of posts - Dependency Injection in Android.
In this post I’d like to summarize my presentation by going through it. Maybe not step by step - I think it’s time to break with the past and not come back to solutions which are not or shouldn’t be used. Jake Wharton talked about history (Guice, Dagger 1), Gregory Kick too (almost a half presentation about Spring, Guice, Dagger 1). I also spent a couple of minutes talking about former solutions. But now it’s time to start from the point where we are currently.
Dependency injection
Dependency injection is all about constructing objects and passing them where they are needed. I won’t delve into theory (visit Wikipedia’s DI definition for it). Instead just imagine simple class: UserManager
with two dependencies UserStore
and ApiService
. Without dependency injection this class would look like this:
Both, UserStore
and ApiService
are constructed and provided inside UserManager
class:
Why this code is a bit problematic? Let’s imagine that, you want to change UserStore
implementation and use SharedPreferences
as a storing mechanism. It needs at least Context
object to create an instance so it should be passed to our UserStore
via constructor. It means that also UserManager
class has to be updated to handle new UserStore
constructor. Now imagine dozen of classes which use UserStore
- all of them have to be updated.
Now take a look at our UserManager
class with dependency injection:
Its dependencies are created and provided from the outside of the class:
Now in the similar situation - we’re changing the implementation of one of its dependencies - there is no need to update UserManager
source code. All its dependencies are provided from the outside so the only place in code we have to update is the place in which we’re constructing UserStore
object.
So what are the advantages of dependency injection usage?
Construction/usage seperation
We’re constructing instances of classes once - usually in other places that these objects are used. Thanks to this approach our code is more modular - all dependencies can be replaced in easy way (as long as they have the same interface) with no impact on logic of our application. Want to change DatabaseUserStore
to SharedPrefsUserStore
? Fine, just take care about public API (to be the same as DatabaseUserStore
) or just implement the same interface.
Unit testing
The true unit testing assumes that the class has to be tested in complete isolation - without knowledge about its dependencies. In practice, based on our UserManager
class here is an example of unit tests which we would write:
And this is possible only with DI - thanks to that UserManager
is completely independent of UserStore
and ApiManager
implementations. We can provide mocks of these classes (in short - mocks are classes with the same public API which does nothing in method calls and/or returns values which we expect) and test UserManager
in isolation from the real implementations of its dependencies.
Independend/concurrent development
Thanks to code modularity (UserStore
can be implemented independently from UserManager
) it’s easy to split the code between programmers. Only the interface of UserStore
has to be known by everyone (especially public methods of UserStore
used in UserManager
). The rest (implementation, logic) can be tested i.e. by unit tests.
Dependency injection frameworks
Besides advantages dependency injection pattern has some drawbacks. One of them is bigger boilerplate. Just imagine simple LoginActivity
class which is implemented with MVP (model-view-presenter) pattern. This class could look like this:
Source code which is responsible only for LoginActivityPresenter
initialization could look like below:
Doesn’t look so friendly, does it?
And this is the problem which DI frameworks resolve. The same code which uses them can look like this one:
Much simpler right? Of course DI frameworks don’t take objects from nowhere - they still have to be initialized and configured in some place in our code. But objects construction is separated from the usage (actually this is the premise of DI pattern). And DI frameworks care about how to wire everything together (how to deliver objects to places in which they are requested).
To be continued
Everything I described above was a light background to Dagger 2 - dependency injection framework which can be used in Android and Java development. In next post I’ll try to go through whole Dagger 2 API. In case you don’t want to wait just try my Github client example which is built on top of Dagger 2 and was used with my presentation. Just a hint - @Modules
and @Components
are the places to construct/provide objects. @Inject
are the places where our objects are used.
More detailed description - soon.
References
- DAGGER 2 - A New Type of dependency injection by Gregory Kick
- The Future of Dependency Injection with Dagger 2 by Jake Wharton
- Dagger 1 to 2 migration process
Author
Miroslaw Stanek
Head of Mobile Development @ Azimo
If you liked this post, you can share it with your followers or follow me on Twitter!