Dagger 2. Part I. Basic principles, graph dependencies, scopes.

Evgenii Matsiuk (Eugene Matsyuk)
AndroidPub
Published in
11 min readMay 6, 2017

Dagger 2 articles cycle:

  1. Dagger 2. Part I. Basic principles, graph dependencies, scopes.
  2. Dagger 2. Part II. Custom scopes, Component dependencies, Subcomponents.
  3. Dagger 2. Part three. New possibilities.

Hello everyone! Currently we have got a lot of instruments, libraries which make android coding easier. You should just track everything and try everything. The Dagger 2 library is one of such tools.

There are many tutorials about this library in the web. But when I started to study Dagger 2, read articles, watched reviews, I found one bad thing. At was rather difficult for me who does not work with Spring and other similar frameworks or libraries to understand the way of dependencies graph creation and providing. Listeners and readers usually have got a lot of code with new annotations. And it should somehow work. Finally there was no full and clear understanding in my head after any review or article.

Now I understand that than I didn’t have good pictures, graphics and schemes which could show “what, where from, where to”. That’s why in my article’s serie I try to feel this gap (add this point). I hope it will help beginners and other persons concerned to understand Dagger 2 better and choose it for own projects. And I can say now that is worth to be done.

At first I wanted to write a single article but I had so many tutorials and pictures and decided to present info divided in small parts. In this case the reader will be able to approach that subject step-by-step.

Theory

First of all I would like to describe some theoretical aspects shortly.
Dagger 2 is a library which helps the developer to implement a pattern of Dependency Injection (one specific form of Inversion of control).

Inversion of control (IoC) principles

  1. The modules of top levels shouldn’t depend on modules of the lower levels. The modules of all levels should depend on abstractions.
  2. The abstractions shouldn’t depend on details. The details should depend on abstractions.

Design disadvantages to be eliminated with IoC

  1. Rigidity. If we change one module the other modules are changed too.
  2. Fragility. If we change one part of program the other parts will have got uncontrolled errors.
  3. Immobility. The single module can be hardly separated from the rest part of the application to be used again.

Dependency Injection (DI)

This is a process to provide any program component with the external dependence. It is a specific form of IoC when it is used at the control of dependencies. In accordance with the single responsibility principle (SRP) the object lets an external specially designed mechanism take general care of building dependencies required. So Dagger 2 also does take care of making such a general mechanism.

Since I expect questions and holy wars concerning IoC, DI, their interaction, I’d like to add that the definitions was taken from Wikipedia and their detailed discussion isn’t a subject of this article.

Now I’d like to tell about the main advantages of the library.

Advantages of Dagger 2

  1. Simple access to shared implementations.
  2. Simple settings of complex dependencies. The big apps usually have a lot of dependencies. Dagger 2 allows you to control all dependences easy.
  3. Simple unit testing and integration testing. We will discuss it in the article about testing with Dagger 2.
  4. “Local” singletons.
  5. Code generation. The received code is clear and available for debugging.
  6. No obfuscation problems. Both Point 5 and 6 are advantage properties of Dagger 2 in comparison with Dagger 1. Dagger 1 worked with reflection. That’s why there were problems with performance, obfuscation, strange errors in runtime.
  7. Small size of the library.

As an example of simple access to the “shared” implementation I use a code:

So @Inject annotations are added to the fields and App.getComponent().inject(this) is added to the method onCreate. Now MainActivity class has an access to the ready implementations of RxUtilsAbs and NetworkUtils.

All these above-mentioned advantages make Dagger 2 the best library for implementation of DI in Android now.
This library certainly has its disadvantages. But we will talk about them at the end of my cycle of articles. Now my task is to get you interested and motivate you to try Dagger 2.

Base elements (annotations) of Dagger 2

  1. @Inject — base annotation whereby the “dependency is requested”
  2. @Module — classes which methods “provide dependencies”
  3. @Provide — methods inside @Module, which “tell Dagger how we want to build and present a dependency“
  4. @Component — bridge between @Inject and @Module
  5. @Scope — enables to create global and local singletons
  6. @Qualifier — if different objects of the same type are necessary

Now, please, just read these annotations for general idea. We will discuss each of them than.
So the theory is finished. It can be studied more carefully by references at the end of my article.
Our main goal is to understand how we can build the whole dependency graph by means of Dagger 2.

Practice

Now we start with the more interesting things.
Let’s study a particular case. Everybody has singletons in the apps. In Android we have singletons because of lifecycles for activity and fragments. Thereat I would like to divide the using singletons into 2 categories:

  1. “Global” singletons which can be required in any part of the app. These are Context, utility classes and other classes which influence the application work.
  2. “Local” singletons to be required in one or several modules only. Because of possible screen reorientation and other reasons the part of logic and data should be often removed to the place independent from the lifecycle. I will describe the “local” singletons in detail in my next article.

I would like to begin with the “global” singletons. How do we usually use them? I suppose the next code should be most times:

SomeSingleton.getInstance().method();

It is common practice. But if we want to use DI pattern this code cannot be satisfied as for several reasons:

  1. There is an unexpected dependency from SomeSingleton in the class where such call is used. This isn’t a clear dependency because it is not clear (either in the constructor or in fields or in methods). That’s why we can see such a dependency only by watching the code of the certain method. But looking at the class interface we can hardly suppose this SomeSingleton to be used here.
  2. The SomeSingleton is performing the initialization process. If the lazy initialization is applied the class using SomeSingleton launches the initialization process (where it is requested at first). So the classes besides their work are responsible for the launching of singleton initialization too.
  3. If we have a lot of such singletons the system is filled with the implicit dependences. Some singletons can depend on the others and it also does not make their further support easy. Also the singletons are staggered in the system and can be in different packages. It could be inconvenient too.

It can be put up with of course. But everything is changed greatly when you want to use unit-tests in your code. You have to do something with those implicit dependences, replace them correctly. And now willing or not you are starting to transform your code into any “testable code”. It is absolutely impossible if you have implicit dependences.

And Dagger 2 is coming. Now we can see how we can implement singletons with DI by means of Dagger 2. We can also see the whole cycle of the dependency graph building.
I would like to begin with the “global” singletons.

Singleton creation

As we can remember @Module is an annotation which marks the class to provide dependences. I will call these classes as Modules then. And I will call the methods providing dependences as provide methods. For example there is a provideNetworkChannel method in ReceiversModule. This method provides the objects of NetworkChannel type. This method can have any name. The most important is that the @Provides annotation ahead of the method and return type (NetworkChannel).
It is common process when return type is an interface or abstract class (RxUtilsAbs) and we initialize and return required implementation (RxUtils) finally.
I am going to describe annotation @Singleton below that’s why we don’t pay attention to it now.
It’s possible to send necessary objects to module and constructor. Example — AppModule.
It’s more interesting with UtilsModule. This module needs objects with types Context and NetworkChannel in order to give it’s dependencies.
That means that we should say to Dagger we need Context and NetworkChannel for creation of RxUtilsAbs and NetworkUtils objects. For that purpose we add arguments Context context for method provideRxUtils and arguments Context context, NetworkChannel networkChannel for provideNetworkUtils method.
In this case the arguments can have any name (context or contextSuper). The type of arguments have a great importance.

Than we create an interface AppComponent with annotation @Component(modules = {AppModule.class, UtilsModule.class, ReceiversModule.class}).
For convenience we name this interface as Component. As above mentioned @Component is a bridge between @Module and @Inject. Or in other words Component is finished dependency graph. What does it mean? We will understand it later.
Using this annotation we say to Dagger that AppComponent consists of three modules: AppModule, UtilsModule, ReceiversModule. The dependencies to be provided by every such module are available for all others modules combined by AppComponent. It’s good illustrated on the picture below.

I think this picture shows well where Dagger 2 gets Context and NetworkChannel objects to build RxUtilsAbs and NetworkUtils from. For example if we remove AppModule from the component annotation than at compilation Dagger2 will conflict and ask where it can get object Context.
Also we declare method void inject(MainActivity mainActivity) in the interface. Using this method we say to Dagger which class we would like to inject into.
I should add that if you want to inject dependencies into another class besides MainActivity (for example SecondActivity) than you have to declare it well in the interface. For example,

The arguments can have any names (mainActivity could be replaced through activity and so on). The type of object to be injected has a main importance! And we can’t use any general types for all classes where we provide dependencies as shown below:

Since Dagger 2 is operated with the code generation but not with the reflection you always have to set concrete types (no abstractions or generics)!

We go further. As you remember we set the MainActivity class as a target for injections in AppComponent. In this class we can use those dependencies provided by modules AppModule, UtilsModule, ReceiversModule. For this purpose we schould add appropriate fields into the class, mark them with the annotation @Inject and make them available (package visibility at least) (If the field is set as private Dagger can’t provide a right implementation thereto).
It should be noted, that the class RxUtils (RxUtils as a child of RxUtilsAbs) should be added into the field RxUtilsAbs rxUtilsAbs as we set in the module UtilsModule.
Further in the method onCreate we add a line
App.getComponent().inject(this);
Since we discuss the creation of singletons the component of our AppComponent should be better saved in the class Application . In our case AppComponent can be accessible through App.getComponent().
By means of the method inject(MainActivity mainActivity) we connect our dependence graph at all. So all dependencies provided by modules AppComponent (Context, NetworkChannel, RxUtilsAbs, NetworkUtils) can be accessible in MainActivity.
Let’s discuss the method buildComponent() of the class App. DaggerAppComponent is not accessible before the compilation.
That’s why at first we should not pay attention to IDE which can say that there is no class DaggerAppComponent at all. IDE cannot also help by builder creation. So we have to write the initialization AppComponent with DaggerAppComponent ”blindly” for the first time.
By the way the code buildComponent() can be reduced:

As we mentioned before Dagger 2 is responsible for the creation of the whole dependency graph. If something is going wrong Dagger 2 will report it by compilation. And we have no unexpected and unclear crashes during runtime as it was with Dagger 1.
Please, look at the diagram below.

Well, we can relax now! The most informative part is over. I think that diagram clearly shows that:

  1. The module provides dependencies. That means that we should declare objects to be provided in the modules.
  2. The component is a dependency graph. It combines modules and provides dependencies for the wanting classes.

If something is unclear or not evident, please add comments. I will correct and explain everything.

At the end I am going to discuss the annotation @Singleton. It is a scope annotation provided by Dagger. If @Singleton is placed before the method which provides a dependency Dagger will create a single version of the marked dependency (or singleton) during initialization of the component. And it will provide that single version by each call of that dependency.

Less words but more pictures!

Every dependency is provided with annotation @Singleton. That means every time when Dagger has to use that dependency it will use its single version only.

Now for comparison we removing annotation @Singleton from the method provideNetworkChannel (the dependency will be unscoped). That means every time when Dagger has to use that dependency it will use new version every time.

We can also create custom scope annotations (more details are in my next article).
Below you can see some features of the scope annotations:

  1. Usually the scope annotations are set for the Component and provide method.
  2. If at least one provide method has a scope annotation the Component should have the same scope annotation.
  3. The Component can be unscoped only if all provide methods in all its modules are unscoped too.
  4. All scope annotations for one component (for all modules with provide methods to be a part of Component and for the Component itself) should be the same.

More details about scope annotations will be described in my next article. That’s all for the first time :)
Well, in this article we studied theoretical aspects of IoC, DI, Dagger 2, had a detailed look at the creation of the dependency graph by means of Dagger 2, studied shortly scope annotations and the actual implementation of @Singleton.

Below you can find articles to be read:

--

--

Evgenii Matsiuk (Eugene Matsyuk)
AndroidPub

Co-Founder at marathonlabs.io | Co-Author of Kaspresso | Android Google Developer Expert