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.
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)”.
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.
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.
@Inject
- the base annotation with which the “dependency is requested”@Module
- classes whose methods “provide dependencies”@Provide
methods inside @Module
, “telling Dagger how we want to design and provide dependency“@Component
- the bridge between @Inject
and @Module
@Scope
- provide the ability to create global and “local singletons”@Qualifier
- if you need different objects of the same typeFor 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.
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:
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:
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
.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.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.
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.
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.
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!
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 lineApp.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!
Fuf, now you can exhale! The most saturated part behind. It seems to me that this scheme clearly demonstrates that:
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!
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 .
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 .
We can also create custom Scope annotations (more details in the next article).
Here are some features of Scope annotations:
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:
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