Reputation: 14286
Let's say I want to create a Repository, which will be my Single Source of Truth. Inside it we will have access to REST calls and database connections for saving the REST calls as a cache.
If I want to inject this Repository through Dagger 2, but I want it to be replaceable (I am avoiding the word "modular") for other Repository implementation, or I want it to be usable on other Components, should I create it as a Repository Module or as a Repository Subcomponent?
When should I use Modules and when should I use Subcomponents to achieve modularity?
Upvotes: 2
Views: 1876
Reputation: 95654
Modules represent groups of related functionality and bindings, and are probably what you're looking for. By creating a documented and reusable Module, you encapsulate the responsibility of creating a Repository, allowing other developers or teams to consume the Repository without knowing how or where to create it. You might even choose to make the constructors of your Repository package-private, so you control how it can be consumed.
Unlike Dagger 1, Dagger 2 expects that Modules are not necessarily complete: they can refer to bindings that they do not define or specify. Consequently, if you want to create a reusable Module that depends on external components, you might want to document what kinds of bindings it needs from outside. (Of course, you can also use Module.includes to specify this yourself, but that prevents consumers from using your module with their choice of dependencies. Substituting dependencies might be an important part of your testing strategy, such as for integration tests using a fake network backend.)
In contrast, a Subcomponent usually represents a different scope and lifecycle. In an Android app this might be the lifetime of a Service, Activity, or Fragment, but you can also define scopes of your own: you might also choose some to represent a user's login scope (i.e. the same objects for as long as the user is logged in, but new objects once the user logs out or logs in as someone else).
However, these two choices aren't either-or, particularly when using subcomponents for encapsulation. If your Repository requires a number of bindings that you don't want to be injectable from the rest of the app, you might choose to bind those in a Module that you only include inside a Subcomponent. That'd look like this:
@Provides Repository provideRepository(RepositorySubcomponent subcomponent) {
return subcomponent.getRepository(); // defined on the subcomponent
}
Similarly, you might also need to bind particular bindings in a sub-graph. Perhaps your app needs two separate repository backends, with two separate storage instances. This might be a problem for creating a reusable Repository, since the same graph couldn't inject different backends for the same binding. (This is sometimes called the "robot legs" problem, imagining a robot that uses identical legs and knees but different left and right feet.) With subcomponents, you can either choose to make the backend part of the builder:
@Provides @Foo Repository provideFooRepository(
RepositorySubcomponent.Builder builder,
StoneTabletStorage stoneTabletStorage) {
// Inject and use the builder instead, since we're passing in a required value.
// In this example, you'd need to define a method on the builder,
// @BindsInstance Builder storage(Storage storageImpl);
return builder
.storage(stoneTabletStorage)
.build()
.getRepository();
}
@Provides @Bar Repository provideBarRepository(
RepositorySubcomponent.Builder builder,
HolographicQubitStorage holographicQubitStorage) {
return subcomponent
.storage(holographicQubitStorage)
.build()
.getRepository();
}
...or define separate subcomponents for each:
@Subcomponent(modules = {RepositoryModule.class, StoneTabletStorageModule.class})
public interface FooSubcomponent {
Repository getFooRepository();
}
@Subcomponent(modules = {RepositoryModule.class, HolographicQubitStorageModule.class})
public interface BarSubcomponent {
Repository getBarRepository();
}
You can also combine these techniques by listing the subcomponent on Modules.subcomponents, thereby creating a reusable module that installs subcomponents as needed. This allows the subcomponent to become an implementation detail of the module, giving you greater flexibility to change the module while preserving the bindings that other developers and teams use.
Upvotes: 6