Modularization. Part 2. Dagger structure
This series consist of
- Part 1 — Application structure overview
- Part 2 — Dagger structure
- Part 3 — Compilation time
The main question is how to organize dependency injection between different application modules.
Dagger provides two mechanisms for building relations between components components (see Component relationships from JavaDoc). Subcomponents and component dependencies.
Subcomponents
We cannot use subcomponents because they are tightly coupled with parents (parent component should know the complete list of subcomponents). In other words, this is the circular dependency between application modules. That contradicts to our initial requirements.
Another disadvantage
the subcomponent implementation inherit the entire binding graph from its parent
This opens possibility to easily “leak” dependency. I prefer to be more explicit about what dependencies should be visible, even if it means writing more code.
But subcomponents can be good choice when are used within same application module.
Component dependencies
In that case, parent component knows nothing about dependent components. Also, we have to explicitly list all classes that we want to be available for dependent components. Let’s see an example. base-app
app-module has LoggedOutComponent
which provides UserRepository
and know nothing about who will use it.
LoginComponent
, from login
app-module, depends on LoggedOutComponent
. This allows to inject UserRepository
into LoginPreseneter
.
How to get reference to base component
LoggedOutComponent
resides in base-app
module which is the bottom level module. login
module depends on it. Actual instance for LoggedOutComponent
is created in App
class inside app
top-level module. We don’t want to introduce the dependency from login
module to app
module. Otherwise, it would mean circular dependency.
In order to mitigate that, base-app
module defines interface LoggedOutComponentProvider
Application has to implement this interface. That allows to get easily access component by casting context.getApplicationContext
to LoggedOutComponentProvider
. That's even easier to do with extension function
and use it inside LoginActivity
This approach has one disadvantage — absence of compile time verification. Developer can forget to implement LoggedOutComponentProvider
and notice that only as runtime error. Fortunately, we can mitigate this disadvantage by having instrumented tests.
Eliminate circular dependency is much more important.
Scopes
“Component dependencies” is very powerful technique. It allows composing different components into one. We could write something like that
Unfortunately, this has limitation. Only one component, from “dependencies array” can have a scope. All other should be without scope. Example above will work, if LoggedOutComponent
has @Singleton
scope and LoggedInComponent
is without scope. But won’t work, if LoggedInComponent
has a scope (@Singleton
or any other).
That’s why we in this repo we introduce LoggedInComponent
which is subcomponent of LoggedOutComponent
. It provides the same UserRepository
and UserName
(which make sense only when the user is logged in).
Summary
- Use “component dependency” for Dagger component’s relation between app-modules.
- Break circular dependencies by introducing “component provider” contract
SubComponent
can be used only within sameapp-module
, because the force circular dependency
Project can be found on Github. Continue reading with Part 3 — Compilation time.