The SOLID principles were introduced for the first time by Robert C. Martin in the early 2000s. This paper explains the five principles even if the acronym SOLID is not used, it will be introduced later. It’s a long time ago but they are still valid today: they are the base of the Clean Architecture used in many Android applications. SOLID is an acronym: the D stands for Dependency Inversion, probably the most complicated (but also the most important) of the five principles.
The Dependency Inversion principle is usually defined in this way:
High level modules should not depend upon low level modules
The high level modules usually contain the business logic of the application, the low level modules the implementation details. For example an algorithm that calculates something is defined in an high level module while the database code is in a low level module.
It’s time to see how to apply this principle on a multi-module Android project and how to leverage Dagger to manage the dependencies organized in this way. Let’s start!
Dependency inversion in practice
Let’s see an example to explain how to use the Dependency Inversion. The
WeatherUseCase class returns the weather conditions based on the device location:
It uses coroutines and other stuff and probably manages the exceptions in a bad way, the important point here is that two dependencies are declared in the constructor. So this class is using the dependency injection, is it using also the dependency inversion? The name is similar but the meaning is different.
To answer this question we need to see a higher level view of the example, the classes can be organized into three modules:
domain module (in this post the word module always refers to an Android Studio module) contains just the
WeatherUseCase class. It depends on the other two modules that define interfaces and implementations of the classes to get the device location and to retrieve the weather from a remote server. Looking at this diagram we can answer the previous question: this example uses the dependency injection but not the dependency inversion. The reason is that, contrary to the principle, the high level module
domain depends upon the low level modules
Dagger in a multi module project
Dagger can be configured in many ways in the previous example, for example using subcomponents, component dependencies or Dagger Android. Here we’ll use component dependencies, an approach similar to the one described in this post.
The Dagger code can be organized in two ways:
- the Dagger code can be defined only in the top level module (usually the application)
- each module can define its own Dagger configuration
The first approach seems clearer because, using it, most of the modules in the project are not aware of Dagger. Although it works well for small projects, when there are many classes to manage I prefer to define the Dagger configuration inside every module.
Going back to the previous example each module can define its own Dagger component, the components defined in
location are really simple:
DomainComponent defined in the
domain module uses the other two components as dependencies:
Here a diagram similar to the previous one to show the dependencies between the components:
WeatherUseCase constructor is then annotated using
In this way Dagger can create a new instance of
WeatherUseCase using the objects provided by
WeatherComponent as constructor arguments.
Let’s invert the dependencies!
How can we apply dependency inversion in this project? The goal is to invert the dependencies,
location should depend upon
domain. The simplest way to achieve it is to move the interfaces
TemperatureRepository to the
Now the dependencies between classes are the same but we can see that the module's dependencies are the opposite of the original example. The low level modules depend upon the high level module, so we are using Dependency Inversion.
The Dependency Inversion is largely used in the Clean Architecture, the main benefit is in common with this architecture:
domainmodule is framework independent , it doesn’t contain any reference to the frameworks used for the implementation
Based on this consideration we have other two important benefits:
- code convention Vs architecture: even without using the Dependency Inversion in many projects the
domaindoesn’t contain any reference to the Android classes and it can be tested using JVM tests. But probably in that project it’s just because of a code convention, developers know that shouldn’t use the Android classes on that module. Using the Dependency Inversion we are 100% sure that Android classes (or other low level framework classes) are not used there, they are not in the classpath so there is a compilation error in case they are used.
- JVM tests are quick: the classpath is really small, it contains just a few dependencies to some utility libraries and no dependencies to other modules. So JVM tests on that module can be executed really fast, enabling TDD. And the
domaincontains the business logic, the code that deserves to be tested.
Using Dagger with inverted dependencies
In the initial example the
DomainComponent depended on the other two components, after inverting the dependencies this code doesn’t compile anymore because the
domain module doesn’t depend on the other two modules.
Dependency Inversion can be used to fix this issue as well, we can remove the dependency on
WeatherComponent and replace them with a new interface
DomainDependencies interface contains the objects needed by
DomainComponent to create a new instance of
WeatherUseCase. It’s important to notice that this new interface is not annotated using
@Component, it’s a standard Kotlin interface with two properties (it works even defining two methods instead of the properties).
DomainDependencies implementation must be defined in a module that depends on all the other modules, usually the application can be used to wire all components. A simple implementation can just delegate to the
The diagram with the component's dependencies is a bit more complicated:
An example similar to this one is available on this post about Dagger on library development. The idea is similar but it’s used to manage something slightly different.
Component dependencies and scopes
DomainDependencies implementation can be created also using a real Dagger component. We can just create an interface annotated with
DomainDependenciesImpl knows how to create the objects defined in
DomainDependencies thanks to the dependencies to the other two components. So no extra methods need to be defined, it just works!
EDIT: It works thanks to a feature introduced in Dagger 2.27. Using older versions a hacky solution is necessary (see 1225 for details).
In a multi module project, organized in a way similar to that described in this post, I think it’s a good idea to define a scoped component for each module. In this way each component can define its own singleton objects. The scope depends on where the components are stored, in this example all the components are saved in the
The three Components defined uses its own scope annotation (for example
WeatherSingleton). This is not required, it works even using the same annotation for each component. It’s important to notice that a custom annotation must be defined because a component scoped using the standard
Singleton annotation cannot depend on other scoped components. However I think that defining multiple annotations the code is clearer and the error messages in case of errors are more explicit.
A real example of an application that can benefit from this organization is an app that uses
Apollo (let’s say that some features use a REST endpoint and other a GraphQL endpoint). In this example we can create four components, each component defines its own singleton (like the
OkHttpClient or the
OkHttp module is used by the other three modules to share the same
OkHttpClient. We can achieve something similar using a single Dagger
Component that defines all the singletons, however using multiple components there is a better separation of concerns and a feature module can include only the modules (and the libraries) that are needed. If a feature module uses only a GraphQL endpoint it can just ignore the module that defines the Retrofit stuff.
Dependency Inversion is a principle that can help a lot to organize a big project in small independent modules. In this post we have seen an example on how to organize Dagger components on a project that apply the Dependency Inversion. The complete example is available on the
dagger branch of this GitHub repository.