If you run the application and try to run login first-username
followed by
login second-username
, you’ll notice that it works! How/why is this working?
The values in the Map<String, Command>
that are available in
CommandProcessorFactory
are [HelloWorldCommand, LoginCommand]
. When we
created the @Subcomponent
for UserCommandsRouter
, it inherited the modules
from CommandProcessorFactory
, and therefore also the values of Map<String,
Command>
. So the full set of values in the Map<String, Command>
for
UserCommandsRouter
is [HelloWorldCommand, LoginCommand, DepositCommand,
WithdrawCommand]
.
There is no way to remove values from the map, but we have another option. We
can add an Optional<Account>
dependency to LoginCommand
to indicate if we’re
in a state where an Account
is available or not:
final class LoginCommand extends SingleArgCommand {
private final Database database;
private final Outputter outputter;
private final UserCommandsRouter.Factory userCommandsRouterFactory;
private final Optional<Account> account;
@Inject
LoginCommand(
Database database,
Outputter outputter,
UserCommandsRouter.Factory userCommandsRouterFactory,
Optional<Account> account) {
this.database = database;
this.outputter = outputter;
this.userCommandsRouterFactory = userCommandsRouterFactory;
this.account = account;
}
@Override
public Result handleArg(String username) {
if (account.isPresent()) {
// Ignore "login <foo>" commands if we already have an account
return Result.handled();
} else {
Account account = database.getAccount(username);
UserCommandsRouter userCommands = userCommandsRouterFactory.create(account);
return Result.enterNestedCommandSet(userCommands.router());
}
}
}
In order to tell Dagger how to supply this Optional<Account>
, let’s add a
@BindsOptionalOf
method to LoginCommandModule
:
@Module
abstract class LoginCommandModule {
...
@BindsOptionalOf
abstract Account optionalAccount();
}
When Dagger sees a @BindsOptionalOf
method, it will use Optional.of(<the
account>)
if an Account
is available; if not, it will use Optional.empty()
(or Optional.absent()
if you’re using the Guava version of Optional
).
Looking back, we can now see that within CommandProcessorFactory
, there is no
Account
, but within UserCommandsRouter
there is. Each will have its own
LoginCommand
that differs in this optional account, and we can therefore run
different logic in both cases!
CONCEPTS
- When Dagger creates sets or maps of objects, the set or map will contain all values from any parent component(s) in addition to the values that are installed by bindings in the component itself.
@BindsOptionalOf
tells Dagger that it can construct instances ofOptional<ReturnType>
. The presence of theOptional
is determined by whether Dagger knows how to create an instance ofReturnType
, and it can be present in a subcomponent but absent in its parent.