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:

UserManager without DI

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:

UserManager with DI

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:

LoginActivity diagram

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

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!

Written on June 2, 2015