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

Evgenii Matsiuk (Eugene Matsyuk)
ProAndroidDev
Published in
8 min readJul 13, 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.

Greetings everyone!

Continue with our Dagger 2 articles cycle. If you haven’t read the first part yet you should do it right now :)
Many thanks to everyone for your reviews and comments.
Here we’re gonna talk custom scopes, components linking via component dependencies and subcomponents. And will touch upon such important subjects as mobile app architecture and how Dagger 2 helps us build cleaner, module-decoupled architecture.

Everyone interested welcome!

Architecture and custom scopes

Let’s start with architecture. Lately this subject is taking a lot of attention, a great number of articles and speeches are devoted to the issue. Subject is undoubtedly important because how we say in Russia: “As you name the boat, so shall it float”. So I strongly advise to read these articles first:

  1. Clean Architecture from uncle Bob
  2. Clean Architecture in Android

I like the Clean Architecture approach very much. It allows to perform clean vertical and horizontal module composition where each entity does exactly what it should. E.g. Fragment is only responsible for displaying UI and not for making network calls, DB calls, business logic and so on, which could otherwise make the Fragment a nasty big tangled bunch of code. I think it’s familiar to a lot of you.

Consider the following example: there’s an app, which has a few modules, and one of them is a chat module. Chat module contains three screens: single chat screen, group chat screen and settings.

Keeping Clean Architecture in mind we can distinguish three horizontal levels:

  1. Global application level. Here we keep objects needed for the whole application lifecycle, i.e. Global Singletons. Let it be Context (Application context), NetworkUtils (utility class) and IDataRepository (class performing network calls).
  2. Chat level. Objects needed for each of three chat screens: IChatInteractor (class implementing particular Chat business cases) and IChatStateController (class responsible for Chat State).
  3. Each screen level. Each screen has its own Presenter, resistant to reorientation (meaning its life-cycle is longer than Activity’s one)

Below you can see these life-cycles on the schematic timeline:

Do you remember we mentioned “local singletons” in the last article? Now then, chat level entities and each chat screen entities represent “local singletons”, i.e. objects which life-cycle is greater than Activity/Fragment lifecycle, but smaller than the whole app lifecycle.

And now Dagger 2 comes into a play. It has amazing mechanism called Scopes. This mechanism takes responsibility of making and keeping a single instance of required class while corresponding Scope exists. I bet the phrase “while corresponding Scope exists” is confusing a bit and spawns questions. Don’t freak out, everything will be cleared below.

Last article we were marking “global singleton” scopes with @Singleton. This scope exists all the time app is alive. But we can create our own custom scope annotations. For example:

And the @Singleton annotation declaration for Dagger 2 looks like this:

That is @Singleton is not different from @ChatScope, the first just happens to be provided by Dagger by default. And the single purpose of these annotations is to point Dagger provide either scoped or unscoped objects. But again, it’s we who are in charge for “scoped” objects lifecycle.

Returning to our example, accordingly to our current architecture, we have three object groups, and each has its own life-cycle. Thus we need three scope annotations:

  1. @Singleton — for global singletons.
  2. @ChatScope — for chat objects.
  3. @ChatScreenScope — for particular chat screen objects.

At the same time we note, that @ChatScope objects must have an access to @Singleton objects, and @ChatScreenScope objects to both @Singleton and @ChatScope objects.

Aforesaid schematically:

Further comes corresponding Dagger components creation:

  1. AppComponent, which provides “global singletons”.
  2. ChatComponent, which provides “local singletons” for all Chat screens.
  3. SCComponent, providing “local singletons” for each particular Chat Screen (SingleChatFragment, i.e. Single Chat Screen).

And again visualizing abovementioned entities:

As a result we got three components with three different scope annotations which sequentially depend on each other. SCComponent depends on ChatComponent which depends on AppComponent.

Now another important question arises: how to properly link these components? There are two options.

Component dependencies

This option migrated from Dagger 1.

Let’s denote Component dependencies traits right away:

  1. Two dependent components cannot have the same Scope. More details here
  2. Parent component must explicitly declare objects which can be used in child components.
  3. Component can depend on other components.

Dependency diagram looks like this for our case:

General scheme of Component dependencies

Let’s look separately into each component with its modules.

AppComponent

Note that inside the AppComponent interface we explicitly declare objects available for child components (but not for its children, so we’ll talk later about this situation). For example if a child component requires NetworkUtils then Dagger gives the corresponding error.

We can still declare injection targets/subjects in interface as well. You should not be misled by the fact that component is a parent, it still can inject dependencies itself into required classes (Activity, Fragment, etc.).

ChatComponent

We explicitly declare the dependency inside ChatComponent annotation (it depends on AppComponent). By the way, as it was mentioned above, component can have few parents (just add new components into its annotation). But the component’s scope-annotations must be different. Also we declare objects which will be accessible to child components inside interface.

Noticed green arrow at the picture above (pic. General scheme of Component dependencies)? As I said before, ChatComponent is able to use AppComponent’s dependencies which are explicitly declared. But ChatComponent’s children aren’t able to use AppComponent’s dependencies, except those that are explicitly declared in ChatComponent (what we did for Context).

SCComponent

SCComponent depends on ChatComponent and injects dependencies into SingleChatFragment. Herewith it can inject both SCPresenter and other parent component’s objects explicitly declared in corresponding interfaces to SingleChatFragment.

Last step remaining is to initialise components:

In contrast to a common component, we have an additional method while initializing dependent components with builders: appComponent(…) (for DaggerChatComponent) and chatComponent(…) (for DaggerSCComponent), where we pass an initialized parent component as an argument.
By the way, if a component has two parents then builder has two methods. Three methods for three parents and so on.
Since each component has its own lifecycle different from the Activity/Fragment lifecycle, we store component instances inside the Application class. Will look into Application class example in the end.

Subcomponents

This one was introduced in Dagger 2.

Traits:

  1. Parent component is obliged to declare Subcomponents getters inside its interface.
  2. Subcomponent has access to all parents objects.
  3. Subcomponent can only have one parent.

Surely Subcomponents have differences from Component Dependencies.
Let’s look into scheme and code to get this clear.

We can see that a child component has access to all parents objects, and this is true throughout the whole dependency tree. For instance, SCComponent has access to NetworkUtils.

AppComponent

Next Subcomponents difference. Let’s create ChatComponent initialization method inside AppComponent interface. The main part is method’s signature: returning type ChatComponent and arguments (ChatModule).

Notice that in the constructor of our ChatModule no objects are required (default constructor), so in the method plusChatComponent you can omit this argument. However, for a clearer picture of dependencies and for educational purposes we’ll leave it as detailed as possible.

ChatComponent

ChatComponent — is in the same time both a child and a parent component. The fact that it is something’s parent is declared by the SCComponent creation method inside the interface. And the fact that it is a child component is pointed out by its @Subcomponent annotation.

SCComponent

As we noted earlier, since each component has its own lifecycle different from Activity/Fragment lifecycle, we perform component instances storing and initialization inside the Application class:

Now we can finally see Application lifecycle in code. So the AppComponent is clear, we initialize it on app startup and do not touch anymore. But ChatComponent and SCComponent are initialized when needed using plusChatComponent() and plusSCComponent() methods. These methods are also responsible for giving the same instance on each call. So when you repeatedly call

scComponent = chatComponent.plusSComponent(new SCModule());

the new SCCComponent instance is built with its own dependency graph.

Using clearChatComponent() and clearSCComponent() methods we can stop life of the existing component with its graphs. Yep, simply nulling references. If we need ChatComponent and SCComponent again we just call plusChatComponent() and plusSCComponent() methods which create new instances.
Just in case I’ll clarify, that in this particular example we cannot initialize SCComponent when ChatComponent is not initialized — we will get a NullPointerException.

But if you have many components I recommend to move this code from MyApp to a special singleton (aka Injector for example) which will be responsible for creating, destroying and providing Dagger components and subcomponents.

That’s it. As you can see, custom scopes, component dependencies and subcomponents are extremely important Dagger 2 elements which allow us to create a more structured and cleaner architecture.

  1. Good article about Dagger 2 in general
  2. About custom scopes from Miroslaw
  3. Component dependencies and subcomponents distinctions

Will be glad for any comments, remarks, questions and likes :)
Next article we’ll look into Dagger 2 usage in testing, and additional but nevertheless important and useful library features.

Authors:
Eugene Matsyuk
Roman Yatsina

--

--