Dagger2 for Modular Architecture
When designing a modular architecture or an Instant App the vertical dependencies can have only one direction: Feature modules depends on Base module, never viceversa.
In this article we will see how before forcing a modular architecture we had a dependency from the Base module to a feature module and how to break this dependency.
You can refer to the following linked article for a better explanation of the problem and what benefits we can get from a Modular Architecture.
Content Table
- Dagger2: Introduction
- A simple component
- SubComponents
- Issue in a Modular Architecture
- Component dependency
- Component dependency — dependency access
- Dependency Graph — components split
Dagger2 Introduction
Dagger 2 is a compile-time evolution approach to dependency injection.
Taking the approach started in Dagger 1.x to its ultimate conclusion, Dagger 2.x eliminates all reflection, and improves code clarity by removing the traditional ObjectGraph/Injector in favor of user-specified @Component interfaces.
This article will not talk about generic Dependency Injection, but will focus on implementation details for Dagger2 in Android.
A simple component
This is how looks a simple dagger ApplicationComponent
exposing some dependencies like a UserRepository
. Let’s see in detail:
ApplicationComponent
: a dagger component exposing some dependencies instances. This usually needs anApplication
reference to build some dependencies like aSharedPrefs
NetworkModule
: a dagger module that provides — create or build — dependencies to the component or dependency graph. Dagger2 will actually create the DI graph based on these modules. For example to build theUserRepository
it needs aSharedPref
as specified in the provide method. SoprovideSharedPref()
will be executed and the result will feedprovideUserRepository(sharedPref)
to provide aUserRepository
- Application: a custom android application that usually build the dagger
ApplicationComponent
passing a reference to itself
A simple component — Code
Let’s take a look to the code. Each component is annotated with @Component
and list all the related modules. Each module is annotated with @Module
and each provide method is annotated with @Provides
.
Dagger2 will generate at build timeDaggerApplicationComponent
that we can build and store to inject later an object or create a Subcomponent that we’ll see later.
SubComponents
Let’s add two activities representing two features: a RecipeBrowserActivity and a LoginActivity. We want two different components extending the ApplicationComponent with new dependencies. We can do this with dagger SubComponent creating a BrowserSubComponent
and a LoginSubComponent
.
A SubComponent is almost like a Component, it will have access to the Dependency Graph of ApplicationComponent and not only the dependencies exposed explicitly by ApplicationComponent.
For example: BrowserSubComponent needs access to provideOkHttp
in ApplicationComponent to build the BrowserService
.
BrowserSubComponents — Code
Issue in a Modular Architecture
Looking closely to the class diagram we can see that ApplicationComponent
has a reference to BrowserSubComponent.Builder
. In a modular architecture, where the ApplicationComponent belongs to the BaseModule and a SubComponent belongs to a feature module, this can’t compile.
The BaseModule doesn’t depend from feature modules, but the opposite. So ApplicationComponent can’t access a Builder class inside a feature module.
We have to revert the direction on this reference — red arrows in the diagram.
How do we do this? Using plain Components for feature modules.
Component dependency
We can’t use anymore subcomponents, but we have to use components with a dependency to the ApplictionComponent. The builders of the features components will have a reference to the ApplicationComponent
, and will be able to access exposed dependencies.
Component dependency — dependency access
BrowserComponent
has access to the dependencies exposed explicitly in the ApplicationComponent
but not the entire dependency graph— i.e. SharedPref
but not okHttp
as the orange arrow in the next picture.
That means we need to explicitly declare each dependency the BrowserComponent
needs to access from the ApplicationComponent
.
Component dependency — code
Dependency Graph — Components split
While your app keep growing, the ApplicationComponent
and its modules will keep growing with dependencies shared between features, and soon could become unmaintainable.
If your ApplicationComponent’s modules have fifty dependencies, you will end having fifty dependencies declared in the same interface/file. This could lead to many merging conflicts in a big team that will drain time from the real development.
A solution could be to create a subcomponent for each module of ApplicationComponent
and then create a facade BaseSubComponent
extending all this subcomponents.
Having all the feature components extending a BaseSubComponent
could generate some boilerplate code, so could be useful to create a BaseInjector
extending AndroidInjector
that will take care to get an instance of the BaseSubComponent
and provide it during the generation of a feature component.
Take a look to BrowserComponent.kt
to see how simple is to create a new feature component extending the BaseSubComponent.