📜 ⬆️ ⬇️

Dagger 2. Part Two. Custom scopes, Component dependencies, Subcomponents

Hello!
We continue our series of articles on Dagger 2. If you have not yet read the first part , do it immediately :)
Many thanks for the feedback and comments on the first part.
In this article we will talk about custom scopes, about linking components through component dependencies and subcomponents. And also we will touch upon such an important question as the architecture of the mobile application, and how Dagger 2 helps us build a more correct, module-independent architecture.
I ask all those interested under the cat!


Architecture and custom scopes


Let's start with architecture. Recently, a lot of attention has been paid to this issue, many articles and speeches are devoted. The question is certainly important, because from the way we call the boat, so it will float. Therefore, I highly recommend to start reading these articles:


  1. Clean Architecture by Uncle Bob
  2. Clean Architecture in Android
  3. Translation into Russian

I really like the Clean Architecture approach in building architecture. It allows for a clear vertical and horizontal construction of all modules, where each class does only what it has to do. For example, Fragment is responsible only for displaying the UI, and not implementing requests to the network, database, implementation of business logic and other things, which made Fragment just a huge piece of tangled code. I think many people know this ..


Consider an example. There is an application. There are several modules in the application, one of which is a chat module. The chat module includes three screens: a single chat screen, a group chat and settings.
Remembering Clean architecture, we distinguish three horizontal levels:


  1. The level of the entire application. Here are the objects that are needed throughout the entire life cycle of the application, that is, "global singletons". Let it be objects: Context (global context), RxUtilsAbs (utility class), NetworkUtils (utility class), and IDataRepository (class responsible for server requests).
  2. Chat level Objects that are needed for all three Chat screens: IChatInteractor (a class that implements specific Chat business cases) and IChatStateController (a class that is responsible for Chat status).
  3. The level of each chat screen. Each screen will have its own Presenter, resistant to reorientation, that is, whose life cycle will be different from the life cycle of the fragment / activation.

Schematically, life cycles will look like this:
image


Remember, in the last article we mentioned the "local" singltons? So, the objects of the chat levels and each chat screen are "local singletons", that is, objects whose life cycle is longer than the life cycle of the standard activation / fragment, but less than the life cycle of the entire application.
But now Dagger 2 comes into play, which has a wonderful Scopes mechanism. This mechanism assumes the creation and storage of a single instance of the required class as long as the corresponding scope exists. I am sure that the phrase "while the existing scope exists" is somewhat confusing and raises questions. Do not worry, everything will become clear below.
In the last article, we tagged the "global singletons" scope @Singleton . This scope has existed for the entire lifetime of the application. But we can also create our own custom scope annotations. For example:


 @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ChatScope { } 

And creating the @Singleton annotation in Dagger 2 looks like this:


 @Scope @Retention(RetentionPolicy.RUNTIME) public @interface Singleton { } 

That is, @Singleton is no different from @ChatScope , just the @Singleton annotation @Singleton provided by the default library. And the purpose of these annotations is one thing - specify Dagger, provide "scope" or "unscoped" objects. But, again, I repeat, we are responsible for the life cycle of the "scope" of objects.
We return to our example. According to the current architecture, we get three groups of objects that have their own "length of life". Thus, we need three scope annotations:


  1. @Singleton - for global singltons.
  2. @ChatScope - for Chat objects.
  3. @ChatScreenScope - for objects of a particular Chat screen.

In this case, we note that @ChatScope objects must have access to @Singleton objects, and @ChatScreenScope - to @Singleton and @ChatScope objects.
Schematically:
image
Next comes the creation and the corresponding component of Dagger:


  1. AppComponent that provides "global singletones".
  2. ChatComponent providing "local singltons" for all Chat screens.
  3. SCComponent , which provides "local singltons" for a specific Chat screen ( SingleChatFragment , i.e., Single Chat screen).

And again we visualize the above:
image
As a result, we obtain three components with three different scope-annotations that are connected to each other in a chain. ChatComponent depends on AppComponent , and SCComponent on ChatComponent .


But now the question arises, how do we properly link these components? There are two ways.


Component dependencies


This communication method was pumping from Dagger 1.
We note immediately the features of Component dependencies:


  1. Two dependent components cannot have the same scope. Similar here .
  2. The parent component in its interface must explicitly specify objects that dependent components can use.
  3. A component may depend on several components.

As in our example, the dependency diagram will look like with component dependencies:
image
Now we will consider each component with its modules separately.


Appcomponent
image
Note that in the interface of the component we explicitly specify the objects that will be available for the child components ( but not for the subsidiaries of the child components , let's talk about this situation a bit later). For example, if the child component wants NetworkUtils , then Dagger will give the corresponding error.
In the interface, we can also still set injection targets. That is, you should not be mistaken that if a component has child components, then it cannot inject its dependencies into the necessary classes (activations / fragments / other).


Chatcomponent
image
In the annotation for ChatComponent we explicitly prescribe what component ChatComponent should depend ChatComponent (depends on AppComponent ). Yes, as it was noted earlier, the component component may have several parents (it is enough just to add new parent components to the annotation). But the scope annotations of the components should be different. And also in the interface we explicitly register those objects to which the child components can have access.
Notice the green arrow? As we have said, ChatComponent can use dependencies on AppComponent , which it explicitly indicated. But the ChatComponent child components ChatComponent no longer use AppComponent , unless we explicitly register these dependencies in ChatComponent , which we actually did for Context .


Sccomponent
image
SCComponent is SCComponent dependent, and it SingleChatFragment dependencies into a SingleChatFragment . In this case, in SingleChatFragment this component can inject both SCPresenter and other objects of the parent components, which are explicitly specified in the corresponding interfaces.


Stayed last step. This is to initialize the components:
image


Compared to a regular component, when the dependent component is initialized in the DaggerChatComponent and DaggerSCComponent , another method appears - appComponent(...) (for DaggerChatComponent ) and chatComponent(...) (for DaggerSCComponent ), into which we specify initialized parent components.
By the way, if a component has two parents, then two corresponding methods appear in the builder. If there are three parents, then there are three methods, etc.
Since all components have their own life cycle, different from the activation / fragment life cycle, we will initialize and store the component instances in the Application file. An example of the Application class is considered at the end.


Subcomponents


The feature is already Dagger2.
Features:


  1. It is necessary to prescribe in the interface of the parent the method of obtaining the subcomponent (the simplified name Subcomponent)
  2. All parent objects are available for the Subcomponent.
  3. There can be only one parent

Yes, Subcomponents have some differences from Component dependencies. Consider the schema and code to better understand the differences.


image


According to the scheme, we see that all objects of the parent are available for the child component, and so on throughout the component dependency tree. For example, SCComponent is available for NetworkUtils .


Appcomponent


image


The next difference is Subcomponents. In the AppComponent interface, AppComponent create a method for the subsequent initialization of ChatComponent . Again, the main thing in this method is the return value ( ChatComponent ) and the arguments ( ChatModule ).
I note that ChatModule don’t need to pass anything to the constructor of our ChatModule (the default constructor), so you can omit this argument in the plusChatComponent method. However, for a clearer picture of dependencies and for educational purposes, for the time being we will leave everything as detailed as possible.


Chatcomponent
image
ChatComponent - is both a child and a parent component. SCComponent parent is indicated by the method of creating an SCComponent in the interface. And the fact that a component is a child is indicated by the @Subcomponent annotation.


Sccomponent
image


As we noted earlier, since all components have their own life cycle, which is different from the activation / fragment life cycle, we will initialize and store the instances of the components in the Application file:


 public class MyApp extends Application { protected static MyApp instance; public static MyApp get() { return instance; } // Dagger 2 components private AppComponent appComponent; private ChatComponent chatComponent; private SCComponent scComponent; @Override public void onCreate() { super.onCreate(); instance = this; // init AppComponent on start of the Application appComponent = DaggerAppComponent.builder() .appModule(new AppModule(instance)) .build(); } public ChatComponent plusChatComponent() { // always get only one instance if (chatComponent == null) { // start lifecycle of chatComponent chatComponent = appComponent.plusChatComponent(new ChatModule()); } return chatComponent; } public void clearChatComponent() { // end lifecycle of chatComponent chatComponent = null; } public SCComponent plusSCComponent() { // always get only one instance if (scComponent == null) { // start lifecycle of scComponent scComponent = chatComponent.plusSComponent(new SCModule()); } return scComponent; } public void clearSCComponent() { // end lifecycle of scComponent scComponent = null; } } 

And now we can finally see the life cycle of the components in the code. Everything is clear about AppComponent , we initialized it when the application started and we don’t touch it anymore. But we initialize ChatComponent and SCComponent as needed using the plusChatComponent() and plusSCComponent . These methods are also responsible for returning single instances of the component.
So when you call again, for example,
scComponent = chatComponent.plusSComponent(new SCModule());
a new instance of SCComponent with its dependency graph.
With the help of the clearChatComponent() and clearSCComponent() methods, we can end the life of the corresponding components with their graphs. Yes, the usual reference links. If ChatComponent and SCComponent needed again, then we simply call the plusChatComponent() and plusSCComponent that create new instances.
Just in case, I’ll clarify that in this example we cannot initialize ChatComponent when SCComponent is not initialized ChatComponent we will snatch a NullPointerException .
Also note that if you have a lot of components and subcomponents, then it is better to put all this code from MyApp into a special singleton (for example, Injector ), which will be responsible for creating, destroying and providing the necessary Dugger components.


That's all. As you have seen, custom scopes, component dependencies and subcomponent are extremely important elements of Dagger 2, with which the developer can create a more structured and correct architecture.
In addition to reading I recommend the following articles:


  1. Very good article about Dagger 2 in general
  2. About custom scopes of the same author
  3. Differences component dependencies from subcomponents

I will be glad to your comments, comments, questions and likes :)
In the next article we will consider the use of Dagger 2 in testing, as well as additional, but no less important and functional features of the library.


')

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


All Articles