Subcomponents are components that inherit and extend the object graph of a parent component. You can use them to partition your application’s object graph into subgraphs either to encapsulate different parts of your application from each other or to use more than one scope within a component.
An object bound in a subcomponent can depend on any object that is bound in its parent component or any ancestor component, in addition to objects that are bound in its own modules. On the other hand, objects bound in parent components can’t depend on those bound in subcomponents; nor can objects bound in one subcomponent depend on objects bound in sibling subcomponents.
In other words, the object graph of a subcomponent’s parent component is a subgraph of the object graph of the subcomponent itself.
Declaring a subcomponent
Just like for top-level components, you create a subcomponent by writing an
abstract class or interface that declares abstract methods that return the
types your application cares about. Instead of annotating a
subcomponent with @Component
, you annotate it with @Subcomponent
and
install @Module
s. Similar to component builders, a
@Subcomponent.Builder
specifies an interface to supply necessary modules to
construct the subcomponent.
@Subcomponent(modules = RequestModule.class)
interface RequestComponent {
RequestHandler requestHandler();
@Subcomponent.Builder
interface Builder {
Builder requestModule(RequestModule module);
RequestComponent build();
}
}
Adding a subcomponent to a parent component
To add a subcomponent to a parent component, add the subcomponent class to the
subcomponents
attribute of a @Module
that the
parent component installs. Then the subcomponent’s builder can be requested from
within the parent.
@Module(subcomponents = RequestComponent.class)
class ServerModule {}
@Singleton
@Component(modules = ServerModule.class)
interface ServerComponent {
RequestRouter requestRouter();
}
@Singleton
class RequestRouter {
private final Provider<RequestComponent.Builder> requestComponentProvider;
@Inject RequestRouter(
Provider<RequestComponent.Builder> requestComponentProvider) {
this.requestComponentProvider = requestComponentProvider;
}
void dataReceived(Data data) {
RequestComponent requestComponent =
requestComponentProvider.get()
.requestModule(new RequestModule(data))
.build();
requestComponent.requestHandler()
.writeResponse(200, "hello, world");
}
}
Subcomponents and scope
One reason to break your application’s component up into subcomponents is to use scopes. With normal, unscoped bindings, each user of an injected type may get a new, separate instance. But if the binding is scoped, then all users of that binding within the scope’s lifetime get the same instance of the bound type.
The standard scope is @Singleton
. Users of singleton-scoped bindings all
get the same instance.
In Dagger, a component can be associated with a scope by annotating it with a
@Scope
annotation. In that case, the component implementation holds
references to all scoped objects so they can be reused. Modules with
@Provides
methods annotated with a scope may only be installed into a
component annotated with the same scope.
(Types with @Inject
constructors may also be annotated with scope
annotations. These “implicit bindings” may be used by any component annotated
with that scope or any of its descendant components. The scoped instance will
be bound in the correct scope.)
No subcomponent may be associated with the same scope as any ancestor component, although two subcomponents that are not mutually reachable can be associated with the same scope because there is no ambiguity about where to store the scoped objects. (The two subcomponents effectively have different scope instances even if they use the same scope annotation.)
For example, in the component tree below, BadChildComponent
has the same
@RootScope
annotation as its parent, RootComponent
, and that is an error.
But SiblingComponentOne
and SiblingComponentTwo
can both use @ChildScope
because there is no way to confuse a binding in one with a binding of the same
type in another.
@RootScope @Component
interface RootComponent {
BadChildComponent.Builder badChildComponent(); // ERROR!
SiblingComponentOne.Builder siblingComponentOne();
SiblingComponentTwo.Builder siblingComponentTwo();
}
@RootScope @Subcomponent
interface BadChildComponent {...}
@ChildScope @Subcomponent
interface SiblingComponentOne {...}
@ChildScope @Subcomponent
interface SiblingComponentTwo {...}
Because a subcomponent is created from within its parent, its lifetime is
strictly smaller than its parent’s. That means that it makes sense to consider
subcomponents’ scopes as “smaller” and parent components’ scopes as “larger”. In
fact, you almost always want the root component to use the @Singleton
scope.
In the example below, RootComponent
is in @Singleton
scope.
@SessionScope
is nested within @Singleton
scope, and @RequestScope
is
nested within @SessionScope
. Note that FooRequestComponent
and
BarRequestComponent
are both associated with @RequestScope
, which works
because they are siblings; neither is an ancestor of the other.
@Singleton @Component
interface RootComponent {
SessionComponent.Builder sessionComponent();
}
@SessionScope @Subcomponent
interface SessionComponent {
FooRequestComponent.Builder fooRequestComponent();
BarRequestComponent.Builder barRequestComponent();
}
@RequestScope @Subcomponent
interface FooRequestComponent {...}
@RequestScope @Subcomponent
interface BarRequestComponent {...}
Subcomponents for encapsulation
Another reason to use subcomponents is to encapsulate different parts of your application from each other. For example, if two services in your server (or two screens in your application) share some bindings, say those used for authentication and authorization, but each have other bindings that really have nothing to do with each other, it might make sense to create separate subcomponents for each service or screen, and to put the shared bindings into the parent component.
In the following example, the Database
is provided within the @Singleton
component, but all of its implementation details are encapsulated within the
DatabaseComponent
. Rest assured that no UI will have access to the
DatabaseConnectionPool
to schedule their own queries without going through the
Database
since that binding only exists in the subcomponent.
@Singleton
@Component(modules = DatabaseModule.class)
interface ApplicationComponent {
Database database();
}
@Module(subcomponents = DatabaseComponent.class)
class DatabaseModule {
@Provides
@Singleton
Database provideDatabase(
@NumberOfCores int numberOfCores,
DatabaseComponent.Builder databaseComponentBuilder) {
return databaseComponentBuilder
.databaseImplModule(new DatabaseImplModule(numberOfCores / 2))
.build()
.database();
}
}
@Module
class DatabaseImplModule {
DatabaseImplModule(int concurrencyLevel) {}
@Provides DatabaseConnectionPool provideDatabaseConnectionPool() {}
@Provides DatabaseSchema provideDatabaseSchema() {}
}
@Subcomponent(modules = DatabaseImplModule.class)
interface DatabaseComponent {
@PrivateToDatabase Database database();
}
Defining subcomponents with abstract factory methods
In addition to @Module.subcomponents
, a subcomponent can be installed in a
parent by declaring an abstract
factory method in the parent component that
returns the subcomponent. If the subcomponent requires a module that does not
have a no-arg public constructor, and that module is not installed into the
parent component, then the factory method must have a parameter of that module’s
type. The factory method may have other parameters for any other modules that
are installed on the subcomponent but not on the parent component. (The
subcomponent will automatically share the instance of any module shared between
it and its parent.) Alternatively, the abstract
method on the parent component
may return the @Subcomponent.Builder
and no modules need to be listed as
parameters.
Using @Module.subcomponents
is better since it allows Dagger to detect if the
subcomponent is ever requested. Installing a subcomponent via a method on the
parent component is an explicit request for that component, even if that method
is never called. Dagger can’t detect that, and thus must generate the
subcomponent even if you never use it.
Details
Extending multibindings
Like other bindings, multibindings in a parent component are visible to bindings in subcomponents. But subcomponents can also add multibindings to maps and sets bound in their parent. Any such additional contributions are visible only to bindings within the subcomponent or its subcomponents, and are not visible within the parent.
@Component(modules = ParentModule.class)
interface Parent {
Map<String, Integer> map();
Set<String> set();
Child child();
}
@Module
class ParentModule {
@Provides @IntoMap
@StringKey("one") static int one() {
return 1;
}
@Provides @IntoMap
@StringKey("two") static int two() {
return 2;
}
@Provides @IntoSet
static String a() {
return "a"
}
@Provides @IntoSet
static String b() {
return "b"
}
}
@Subcomponent(modules = ChildModule.class)
interface Child {
Map<String, Integer> map();
Set<String> set();
}
@Module
class ChildModule {
@Provides @IntoMap
@StringKey("three") static int three() {
return 3;
}
@Provides @IntoMap
@StringKey("four") static int four() {
return 4;
}
@Provides @IntoSet
static String c() {
return "c"
}
@Provides @IntoSet
static String d() {
return "d"
}
}
Parent parent = DaggerParent.create();
Child child = parent.child();
assertThat(parent.map().keySet()).containsExactly("one", "two");
assertThat(child.map().keySet()).containsExactly("one", "two", "three", "four");
assertThat(parent.set()).containsExactly("a", "b");
assertThat(child.set()).containsExactly("a", "b", "c", "d");
Note: In this example we didn’t follow the recommended best practice for installing the subcomponent in the parent component for simplicity only. It is best is to use an installation module rather than declaring an abstract factory method in the parent component. best practice
Repeated modules
When the same module type is installed in a component and any of its subcomponents, then each of those components will automatically use the same instance of the module. This means that it is an error if you call a subcomponent builder method for a repeated module or if a subcomponent factory method defines a repeated module as a parameter. (The former cannot be checked at compile time, and is thus a runtime error.)
@Component(modules = {RepeatedModule.class, ...})
interface ComponentOne {
ComponentTwo componentTwo(RepeatedModule repeatedModule); // COMPILE ERROR!
ComponentThree.Builder componentThreeBuilder();
}
@Subcomponent(modules = {RepeatedModule.class, ...})
interface ComponentTwo { ... }
@Subcomponent(modules = {RepeatedModule.class, ...})
interface ComponentThree {
@Subcomponent.Builder
interface Builder {
Builder repeatedModule(RepeatedModule repeatedModule);
ComponentThree build();
}
}
DaggerComponentOne.create().componentThreeBuilder()
.repeatedModule(new RepeatedModule()) // UnsupportedOperationException!
.build();