📜 ⬆️ ⬇️

Dagger 2. Part One. Basics, creating a dependency graph, Scopes

Hello! Recently, many tools, libraries, have appeared that make it much easier to write code for Android. Just manage to follow everything and try everything. One such tool is the Dagger 2 library.


The network has a lot of different material on this library. But when I was just starting to get familiar with Dagger 2, read articles, watched reports, I found one common flaw in all of this - I, as a person who did not work with Spring and other similar frameworks / libraries, found it rather difficult to understand where the dependencies come from how they are "provided" and what's going on there. A large amount of code with new annotations usually falls out on listeners / readers. And it somehow worked. As a result, after the report / article in my head, everything could not be formed into a single clear picture.


Now, looking back, I understand that I really lacked a schematic display, pictures, clearly showing "what, where and where." Therefore, in my series of articles I will try to fill this gap. I hope this will help beginners and all interested to better understand Dagger 2 and decide to try it in your project. I can immediately say it is worth it.


And yes, initially I wanted to write one article, but there was a lot of material and pictures, so I’ll post the information in small portions so that the reader can gradually dive into the topic.


Theory


Let's quickly run through the theoretical aspects.
Dagger 2 is a library that helps a developer implement the Dependency Injection (Dependency Injection) pattern, which in turn is a “specific form of inversion of control (Inversion of control)”.


Principles of inversion control


  1. The modules of the upper levels should not depend on the modules of the lower levels. Modules of both levels should depend on abstractions.
  2. Abstractions should not depend on the details. Details must depend on abstractions.

Disadvantages of design that are eliminated with the use of Inversion Control


  1. Rigidity. Changing one module leads to changing other modules.
  2. Fragility. Changes in one part lead to uncontrollable errors in other parts of the program.
  3. Immobility. The module is difficult to separate from the rest of the application for reuse.

Dependency Injection (DI)


The process of providing external dependency software component. It is a specific form of “inversion of control” (English Inversion of control, IoC), when it is applied to dependency management. In full accordance with the principle of a single duty, the object gives care to build the dependencies required by it to an external, specially designed general mechanism for this.
So, Dagger 2 just takes care of creating this common mechanism.


Anticipating the questions and holivars on IoC, DI, how they relate to each other, I will add that the definitions were taken from Wikipedia, and a detailed discussion is beyond the scope of the article.


Now we list the main advantages of the library.


Advantages of Dagger 2


  1. Easy access to “shared” implementations.
  2. Simple setup of complex dependencies. The more application you have, the more dependencies become. Dagger 2 allows you to still control all dependencies easily.
  3. Facilitate Unit testing and integration testing. This issue will be discussed in an article dedicated to testing with Dagger 2.
  4. “Local” singletons.
  5. Code Generation The resulting code is clear and accessible for debugging.
  6. No problems with obfuscation. Both the fifth and sixth points are the distinguishing features of the second version of the Dagger. Dagger 1 worked on reflection. From here problems with productivity, obfuskatsiya, mysterious falling in rantayme.
  7. Small library size

For an example of simple access to “shared” implementations, here is the code:


public class MainActivity extends AppCompatActivity { @Inject RxUtilsAbs rxUtilsAbs; @Inject NetworkUtils networkUtils; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); App.getComponent().inject(this); } } 

That is, @Inject annotations are added to the fields and App.getComponent().inject(this); added to the onCreate method App.getComponent().inject(this); . And now the MainActivity class MainActivity ready-made RxUtilsAbs and NetworkUtils .


All these advantages listed above make Dagger 2 the best library for implementing DI on Android at the moment.
Of course, the library has drawbacks. But we will talk about them at the end of a series of articles. Now my task is to interest you and push Dagger 2 to try.


The main elements (annotations) of Dagger 2:


  1. @Inject - the base annotation with which the “dependency is requested”
  2. @Module - classes whose methods “provide dependencies”
  3. @Provide methods inside @Module , “telling Dagger how we want to design and provide dependency“
  4. @Component - the bridge between @Inject and @Module
  5. @Scope - provide the ability to create global and “local singletons”
  6. @Qualifier - if you need different objects of the same type

For now, just review these annotations for general information. We will discuss each of them in detail.
Actually, according to the theory we limit ourselves to this. More information can be found on the links at the end of the article.
Our main goal is to understand how the entire dependency graph is constructed using Dagger 2.


Practice


Now more interesting things begin.
Consider a specific example. All applications have singletons. Android without them is nowhere, considering the life cycles of activations and fragments.
At the same time, I would divide the existing singletons into two categories:


  1. "Global" singletons that may be needed in any part of the application. These include Context, utility classes and other classes that affect the entire application.
  2. "Local" singletons, which are needed only in a specific one or several modules. But because of possible reorientations of the screen and other things, it is often necessary to remove part of the logic and data to a place independent of the life cycle. On the "local" singletons in more detail and schematically in the next article.

Let's start with the "global" singltonov. How do we usually use them? I dare to assume that in most cases the following code takes place:


 SomeSingleton.getInstance().method(); 

Simple practice. But if we want to apply the DI pattern, then this code will not be satisfactory for several reasons:


  1. In a class where a similar call is used, there is suddenly a dependency on the SomeSingleton class. This is an implicit dependency; it is not clearly indicated anywhere (neither in the constructor, nor in the fields, nor in the methods). Therefore, one can only see such a dependency by looking at the code of a specific method, and after all, by the class interface, one cannot say that this SomeSingleton is SomeSingleton .
  2. The initialization process is handled by SomeSingleton itself. And if lazy initialization is used, then the initialization process starts for one of the classes using SomeSingleton (where it will be called first). That is, the classes, in addition to their work, are also responsible for the start of the initialization of Singleton.
  3. With the increase in the number of such Singletones, the system is covered by a network of implicit dependencies. One more singletons may depend on others, which also does not simplify their further maintenance. Plus, singletons are scattered around the system, can be in different packages, and this causes some inconvenience.

With this all, of course, you can live. Not easy, but possible. But everything starts to change radically when you want to impose your unit code on your code. Here you have to do something with these implicit dependencies, somehow make their correct "substitution." You start willy-nilly to convert your code into "tested code", and with implicit dependencies it is unreal.


And now about Dagger 2 (in the course of the article I will sometimes call it simply in Russian - "Dagger"). Now we will see how using Dagger 2 you can implement DI singletones. And at the same time we will see the whole cycle of creating a dependency graph.
Let's start with the "global" singltonov.


Creating singletons


image
As we remember, @Module is an annotation marking a class whose methods "provide dependencies" ("provide dependencies"). In the following, we will call such classes simply Modules. And the methods that "provide dependencies" or "provide dependencies" will be called provide-methods.
For example, in ReceiversModule there is the method provideNetworkChannel , which just provides an object of type NetworkChannel . This method can actually be called whatever you like, the most important is the @Provides annotation before the method and the return type ( NetworkChannel ).
It is common practice when the return type is an interface or an abstract class ( RxUtilsAbs ), and inside the method we already initialize and return the desired implementation ( RxUtils ).
About the @Singleton annotation below, until we pay attention to it.
Also in the module, in the constructor, you can transfer the necessary objects. Example - AppModule .
And with UtilsModule already more interesting. To provide its dependencies - RxUtilsAbs and NetworkUtils it needs objects of the types Context and NetworkChannel . So we have to somehow tell Dagger that when creating RxUtilsAbs and NetworkUtils , Context and NetworkChannel . To do this, the provideRxUtils provideNetworkUtils are added to the provideRxUtils and provideNetworkUtils methods: Context context for the first and Context context, NetworkChannel networkChannel for the second.
In this case, the name of the arguments can be any, even the context , although the contextSuper , without a difference. The main thing is the types of arguments.


image
Next, create an AppComponent interface with annotation
@Component(modules = {AppModule.class, UtilsModule.class, ReceiversModule.class}) .
For convenience, we will call this interface a Component.
As mentioned above, @Component is essentially a bridge between @Module and @Inject . Or in other words, the Component is a ready-made dependency graph. What does this mean? Let's understand a little lower.
With this annotation we tell Dagger that AppComponent contains three modules - AppModule, UtilsModule, ReceiversModule . The dependencies that each of these modules provides are available to all other modules combined under the auspices of the AppComponent component. For greater clarity, take a look at the picture.


image


I think using this picture will make it much clearer where Dagger takes Context and NetworkChannel to build RxUtilsAbs and NetworkUtils . If you remove the AppModule module from the component annotation, for example, when compiling, Dagger will scream and ask where he will get the Context object from.
Also inside the interface we declare the void inject(MainActivity mainActivity) method void inject(MainActivity mainActivity) . With this method, we tell Dagger what class / classes we want to inject.
I’ll add that if you need to inject dependencies into another class other than MainActivity (for example, in SecondActivity ), then this should be clearly spelled out in the interface. For example,


 @Component(modules = {AppModule.class, UtilsModule.class, ReceiversModule.class}) @Singleton public interface AppComponent { void inject(MainActivity mainActivity); void inject(SecondActivity secondActivity); } 

The name of the arguments can be any ( mainActivity can be changed to activity , etc.). The most important type of object where we are going to inject! And it is impossible to use for all classes into which we "push dependences" any generalization of the type:


 @Component(modules = {AppModule.class, UtilsModule.class, ReceiversModule.class}) @Singleton public interface AppComponent { void inject(Object object); } 

Because Dagger 2 works on code generation, not reflection! Types should always be clearly indicated!


image


We go further. Remember, we in the AppComponent injection target set the class MainActivity . In this class, we can use those dependencies that provide the modules AppModule, UtilsModule, ReceiversModule . To do this, simply add the appropriate fields to the class and annotate them with @Inject annotation, and also make their accessibility at least batch (if the field is set as private , Dagger will not be able to substitute the desired implementation into this field).
Also note that in the RxUtilsAbs rxUtilsAbs field, the RxUtilsAbs rxUtilsAbs class is RxUtils ( RxUtils is the successor of RxUtilsAbs ), that is, what we specified in the UtilsModule module.
Next in the onCreate method onCreate we add the line
App.getComponent().inject(this);
Since we are considering creating singletons, the component of our AppComponent best stored in the Application class. In our example, you can access AppComponent through App.getComponent() .
By calling the inject(MainActivity mainActivity) method inject(MainActivity mainActivity) , we finally connect our dependency graph. Thus, all the dependencies that AppComponent modules AppComponent ( Context , NetworkChannel , RxUtilsAbs , NetworkUtils ) become available in MainActivity .
Note the buildComponent() method of the App class. DaggerAppComponent is not available to us before compiling.
Therefore, in the beginning, we do not pay attention to the IDE, which will say that the DaggerAppComponent class DaggerAppComponent not exist. Well, another IDE will not prompt when building a builder. So initializing AppComponent with the help of DaggerAppComponent will have to write "blindly" for the first time.
By the way, the buildComponent() code can be shortened:


 protected AppComponent buildComponent() { return DaggerAppComponent.builder() .appModule(new AppModule(this)) .build(); } 

Dagger 2, as we have said, is responsible for creating the entire dependency graph. If something goes wrong, it will tell you when compiling. No unexpected and incomprehensible crashes in runtime, as it was, for example, with Dagger 1.
And now attention to the diagram below!


image


Fuf, now you can exhale! The most saturated part behind. It seems to me that this scheme clearly demonstrates that:


  1. The module will provide dependencies. That is, in the modules we prescribe what objects we want to provide.
  2. The component is a dependency graph. It combines modules and provides dependencies to classes that need it ( MainActivity )

If something is not clear or not explicitly, write in the comments, correct and explain!


And finally, consider the @Singleton annotation. This is the scope annotation provided by Dagger. If the @Singleton placed in front of the method that provides the dependency, then Dagger, when the Component is initialized, creates a single instance of the marked dependency, that is, the singleton. And with each request for this dependency, this single instance will provide.
Less words, more pictures!
image
Each dependency is provided with the @Singleton annotation. This means that every time Dagger needs to use this dependency, he will only use one instance of it .
image


Now for comparison, remove the provideNetworkChannel annotation from the provideNetworkChannel method (the dependency becomes "unscoped"). This means that when Dagger needs to use this dependency, he will create a new instance of it every time .
image
image


We can also create custom Scope annotations (more details in the next article).
Here are some features of Scope annotations:


  1. Usually scope-annotations are set for the Component and the provide-method.
  2. If at least one provide-method has a scope-annotation, then the Component must have exactly the same scope-annotation .
  3. A component can be "unscoped" only if in all its modules all provide-methods are also "unscoped".
  4. All scope annotations within one component (that is, for all modules with the provide-methods included in the Component and the Component itself) should be the same .

The topic with Scope annotations will be covered in more detail in the next article. And for starters, that's enough for us :)
So, in this article we got acquainted with the theoretical aspects of IoC, DI, Dagger 2. We considered in detail the creation of a dependency graph using Dagger 2, partially we got acquainted with the scope annotations and its specific implementation of @Singleton .


Here is a list of articles that I recommend reading:


  1. Official library page
  2. Google Presentation
  3. Article Fernando Cejas
  4. Article from Miroslaw Stanek
  5. The first part of a series of articles from Antonio Leiva
  6. Good article with shemki

The second article about custom scopes, component dependencies and subcomponents is already waiting for you!
Waiting for comments, feedback and questions!


')

Source: https://habr.com/ru/post/279125/


All Articles