03 July

Dagger 2 – about (sub)components in a few words

When getting into the subject of dependency injection, we need to understand numerous new techniques that enable us to control created objects. Dagger allows developers to declare interfaces that will handle that. We can create an interface which will be a component or a subcomponent. What is the difference?

One of the first decisions we have to make when integrating Dagger (details of its integration were already described on our blog), will be choosing between components and subcomponents. By design, these two components are assigned the same task: they define modules that will deliver dependencies, declare scope of their availability and provide a functionality that enables dependency injection.

Before we look at the differences, let us first revise what does a declaration of a basic component look like:

@Singleton
@Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {
}

The interface will be treated by the Dagger pre-compiler as a component, thanks to @Component annotation. Keep in mind that each project must have at least one base component.

Component

Base component was described in the previous paragraph. When declaring consecutive components we don't need to change anything in it. However we will frequently create objects that will be needed later. By default, any objects created in one module are not visible in other. To share the object we must declare in the component that the object is shared (important is the type returned by the function, not its name):

@Singleton
@Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {
    Application application();
}

Next, we need to declare a derivative component that is dependent on it:

@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {

    void inject(ComponentActivity activity);

}

Thanks to assigning class ApplicationComponent to the dependencies variable in the annotation of component, we can now use all the objects shared by it. In our case it is the Application object.

Subcomponent

Declaration of sub-components is handled a little differently. The base component is involved in creation of its subcomponents. This means that we have to change it every time we add a new subcomponent. Thanks to this link we don't have to worry about sharing facilities of a base component. By default, all objects that are created in modules of base component are available in the subcomponents. An example of a declaration of a component with a subcomponent might look like this:

@Singleton
@Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {

    ActivitySubcomponent plus(ActivitySubModule subModule);
}
@PerActivity
@Subcomponent(modules = {ActivitySubModule.class})
public interface ActivitySubcomponent {

    void inject(SubcomponentActivity activity);
}

In the Component we added a method which takes as an argument a module of subcomponent and returns the same subcomponent. In this way, we told the base component that it is supposed to create a subcomponent. In our code, this way we will get an instance of our component and will be able to perform an injection on it. The module itself, which was an argument of the function in the base component, was declared as a module of our subcomponent. It is worth noting that the @Subcomponent annotation allows us only to declare our modules as opposed to @Component, which facilitates adding dependencies through the dependencies variable.

Conclusions

We can build a graph of dependencies in our application using components and subcomponents. They give very similar options of creating both objects and their scopes. The main difference is the way of passing objects between the dependent modules. Using components gives a programmer more control over which objects may be used in the derivative components. Also important is the fact that we don't need to add new features to the base component each time, which fits very well with the idea of Open-Closed.

In the case of subcomponents, we immediately have access to all objects of the base component. In most cases, a better practice for our applications would be to use common components. This would make our code cleaner and more resistant to changes. We should use subcomponents in situations when both components are strongly related logically and when a derivative component uses most of the objects of the base component.