Reputation: 1
I implemented Dagger tutorial till end of https://dagger.dev/tutorial/11-withdraw-command. In compiling the program I receive this error
java: [Dagger/MissingBinding] main.Database.Account cannot be provided without an @Inject constructor or an @Provides-annotated method. main.Database.Account is injected at main.DepositCommand(account, …) main.DepositCommand is injected at main.UserCommandsModule.depositCommand(command) java.util.Map<java.lang.String,main.Command> is injected at main.CommandRouter(commands) main.CommandRouter is injected at main.CommandProcessor(firstCommandRouter) main.CommandProcessor is requested at main.CommandProcessorFactory.commandProcessor() It is also requested at: main.WithdrawCommand(…, account, …) The following other entry points also depend on it: main.UserCommandsRouter.router() [main.CommandProcessorFactory → main.UserCommandsRouter]
Please help to solve this error.
Thanks alot
My AmountModule class:
import dagger.Module;
import dagger.Provides;
import java.math.BigDecimal;
@Module
interface AmountsModule {
@Provides
@MinimumBalance
static BigDecimal minimumBalance() {
return BigDecimal.ZERO;
}
@Provides
@MaximumWithdrawal
static BigDecimal maximumWithdrawal() {
return new BigDecimal(1000);
}
}
BigDecimal Class:
import javax.inject.Inject;
import java.math.BigDecimal;
abstract class BigDecimalCommand extends SingleArgCommand {
private final Outputter outputter;
protected BigDecimalCommand(Outputter outputter) {
this.outputter = outputter;
}
@Override
protected final Result handleArg(String arg) {
BigDecimal amount = tryParse(arg);
if (amount == null) {
outputter.output(arg + " is not a valid number");
} else if (amount.signum() <= 0) {
outputter.output("amount must be positive");
} else {
handleAmount(amount);
}
return Result.handled();
}
private static BigDecimal tryParse(String arg) {
try {
return new BigDecimal(arg);
} catch (NumberFormatException e) {
return null;
}
}
/** Handles the given (positive) {@code amount} of money. */
protected abstract void handleAmount(BigDecimal amount);
}
Command Class:
import java.util.List;
import java.util.Optional;
public interface Command {
/**
* String token that signifies this command should be selected (e.g.:
* "deposit", "withdraw")
*/
//String key();
/**
* Process the rest of the command's words and do something.
*/
Result handleInput(List<String> input);
final class Result {
private final Status status;
private final Optional<CommandRouter> nestedCommandRouter;
private Result(Status status, Optional<CommandRouter> nestedCommandRouter) {
this.status = status;
this.nestedCommandRouter = nestedCommandRouter;
}
static Result invalid() {
return new Result(Status.INVALID, Optional.empty());
}
static Result handled() {
return new Result(Status.HANDLED, Optional.empty());
}
Status status() {
return status;
}
Optional<CommandRouter> nestedCommandRouter() {
return nestedCommandRouter;
}
static Result enterNestedCommandSet(CommandRouter nestedCommandRouter) {
return new Result(Status.HANDLED, Optional.of(nestedCommandRouter));
}
}
enum Status {
INVALID,
HANDLED,
INPUT_COMPLETED
}
}
CommandProcessor class:
import java.util.ArrayDeque;
import java.util.Deque;
import main.Command.*;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
final class CommandProcessor {
private final Deque<CommandRouter> commandRouterStack = new ArrayDeque<>();
@Inject
CommandProcessor(CommandRouter firstCommandRouter) {
commandRouterStack.push(firstCommandRouter);
}
Status process(String input) {
Result result = commandRouterStack.peek().route(input);
if (result.status().equals(Status.INPUT_COMPLETED)) {
commandRouterStack.pop();
return commandRouterStack.isEmpty()
? Status.INPUT_COMPLETED : Status.HANDLED;
}
result.nestedCommandRouter().ifPresent(commandRouterStack::push);
return result.status();
}
}
CommandProcessorFactory class:
import dagger.Component;
import javax.inject.Singleton;
//@Component(modules = {HelloWorldModule.class,SystemOutModule.class})
@Singleton
@Component(modules = {
LoginCommandModule.class,
HelloWorldModule.class,
SystemOutModule.class,
UserCommandsModule.class,
UserCommandsRouter.InstallationModule.class,
AmountsModule.class
})
public interface CommandProcessorFactory {
CommandProcessor commandProcessor();
}
CommandRouter Class:
import javax.inject.Inject;
import java.util.*;
import main.Command.*;
final public class CommandRouter {
private final Map<String, Command> commands;
// @Inject
// CommandRouter(HelloWorldCommand helloWorldCommand) {
// commands.put(helloWorldCommand.key(), helloWorldCommand);
// }
@Inject
CommandRouter(Map<String,Command> commands){
this.commands = commands;
}
Result route(String input) {
List<String> splitInput = split(input);
if (splitInput.isEmpty()) {
return invalidCommand(input);
}
String commandKey = splitInput.get(0);
Command command = commands.get(commandKey);
if (command == null) {
return invalidCommand(input);
}
List<String> args = splitInput.subList(1, splitInput.size());
Result result = command.handleInput(args);
return result.status().equals(Status.INVALID) ?
invalidCommand(input) : result;
}
private Result invalidCommand(String input) {
System.out.println(
String.format("couldn't understand \"%s\". please try again.", input));
return Result.invalid();
}
// Split on whitespace
private static List<String> split(String input) {
return Arrays.asList(input.trim().split("\\s+"));
}
}
Datebase Class:
import javax.inject.Inject;
import javax.inject.Singleton;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
@Singleton
public class Database {
private final Map<String, Account> accounts = new HashMap<>();
@Inject
Database() {
System.out.println(this);
}
Account getAccount(String username) {
System.out.println("getAccount InDB");
return accounts.computeIfAbsent(username, Account::new);
}
static final class Account {
private final String username;
private BigDecimal balance = BigDecimal.ZERO;
Account(String username) {
this.username = username;
}
String username() {
return username;
}
BigDecimal balance() {
return balance;
}
void deposit(BigDecimal amount) {
balance = balance.add(amount);
}
void withdraw(BigDecimal amount) {
balance = balance.subtract(amount);
}
}
}
DepositCommand Class:
import java.math.BigDecimal;
import java.util.List;
import main.Database.*;
import javax.inject.Inject;
final class DepositCommand extends BigDecimalCommand {
private final Account account;
private final Outputter outputter;
@Inject
DepositCommand(Account account, Outputter outputter) {
super(outputter);
this.account = account;
this.outputter = outputter;
}
@Override
public void handleAmount(BigDecimal amount) {
account.deposit(amount);
outputter.output(account.username() + " now has: " + account.balance());
}
}
HelloWorldCommand Class:
import javax.inject.Inject;
import java.util.List;
public class HelloWorldCommand implements Command {
private final Outputter outputter;
@Inject
HelloWorldCommand(Outputter outputter) {
this.outputter = outputter;
}
// @Override
// public String key() {
// return "hellom";
// }
@Override
public Result handleInput(List<String> input) {
if (!input.isEmpty()) {
return Result.invalid();
}
outputter.output("World!!");
return Result.handled();
}
}
HelloworldModule:
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;
@Module
abstract public class HelloWorldModule {
@Binds
@IntoMap
@StringKey("hello")
abstract Command helloWorldCommand(HelloWorldCommand command);
}
LoginCommand Class:
import javax.inject.Inject;
import main.Database.*;
public class LoginCommand extends SingleArgCommand{
private final Outputter outputter;
private final Database database;
private final UserCommandsRouter.Factory userCommandsRouterFactory;
@Inject
LoginCommand(Database database
,Outputter outputter
,UserCommandsRouter.Factory userCommandsRouterFactory) {
this.outputter = outputter;
this.database = database;
this.userCommandsRouterFactory = userCommandsRouterFactory;
System.out.println(this);
}
// @Override
// public String key() {
// return "login";
// }
@Override
public Result handleArg(String username) {
Account account = database.getAccount(username);
outputter.output(
username + " is logged in with balance: " + account.balance());
return Result.enterNestedCommandSet(
userCommandsRouterFactory.create(account).router());
}
}
LoginCommandModule
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;
@Module
abstract class LoginCommandModule {
@Binds
@IntoMap
@StringKey("login")
abstract Command loginCommand(LoginCommand command);
}
Main class
import java.util.Scanner;
import main.Command.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// CommandRouter commandRouter = new CommandRouter();
// CommandProcessorFactory commandProcessorFactory =
// DaggerCommandRouterFactory.create();
// CommandRouter commandRouter = commandProcessorFactory.router();
CommandProcessorFactory commandProcessorFactory =
DaggerCommandProcessorFactory.create();
CommandProcessor commandProcessor =
commandProcessorFactory.commandProcessor();
while (scanner.hasNextLine()) {
// Status unused = commandProcessor.process(scanner.nextLine());
}
}
}
MaximumWithdrawal annotation
import javax.inject.Qualifier;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Retention(RUNTIME)
public @interface MaximumWithdrawal {
}
MinimumBalance annotation:
import javax.inject.Qualifier;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Retention(RUNTIME)
public @interface MinimumBalance {
}
Outputter Interface:
public interface Outputter {
void output(String output);
}
SingleArgCommand:
import java.util.List;
abstract class SingleArgCommand implements Command {
@Override
public final Result handleInput(List<String> input) {
return input.size() == 1 ? handleArg(input.get(0)) : Result.invalid();
}
/** Handles the single argument to the command. */
protected abstract Result handleArg(String arg);
}
SystemOutModule:
import dagger.Module;
import dagger.Provides;
@Module
abstract public class SystemOutModule {
@Provides
static Outputter textOutputter() {
return System.out::println;
}
}
UserCommandsModule:
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;```
@Module
abstract class UserCommandsModule {
@Binds
@IntoMap
@StringKey("deposit")
abstract Command depositCommand(DepositCommand command);
@Binds
@IntoMap
@StringKey("withdraw")
abstract Command withdrawCommand(WithdrawCommand command);
}
UsercommandRouter:
import dagger.BindsInstance;
import dagger.Module;
import dagger.Subcomponent;
import main.Database.*;
@Subcomponent(modules = { AmountsModule.class,UserCommandsModule.class})
interface UserCommandsRouter {
CommandRouter router();
@Subcomponent.Factory
interface Factory {
UserCommandsRouter create(@BindsInstance Account account);
}
@Module(subcomponents = UserCommandsRouter.class)
interface InstallationModule {}
}
usename Annotation:
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Username {
}
withdrawCommand Class:
import javax.inject.Inject;
import java.math.BigDecimal;
import main.Database.*;
public class WithdrawCommand extends BigDecimalCommand
{
private final Account account;
private final Outputter outputter;
private BigDecimal minimumBalance;
private BigDecimal maximumWithdrawal;
@Inject
WithdrawCommand(Outputter outputter,
Account account,
@MinimumBalance BigDecimal minimumBalance,
@MaximumWithdrawal BigDecimal maximumWithdrawal) {
super(outputter);
this.account = account;
this.outputter = outputter;
this.maximumWithdrawal = maximumWithdrawal;
this.minimumBalance = minimumBalance;
}
@Override
public void handleAmount(BigDecimal amount) {
if (amount.compareTo(maximumWithdrawal) > 0) {
outputter.output("Maximum withdrawal is violated");
return;
}
BigDecimal newBalance = account.balance().subtract(amount);
if (newBalance.compareTo(minimumBalance) < 0) {
outputter.output("Minimum Balance is violated by user.!!");
} else {
account.withdraw(amount);
outputter.output("your new balance is: " + account.balance());
}
}
}
Upvotes: 0
Views: 293
Reputation: 1035
UserCommandsModule
is added to the module list in the @Subcomponent
annotated UserCommandsRouter
. In UserCommandsRouter
, the Factory
subinterface has UserCommandsRouter create(@BindsInstance account)
method defined.
Now, this account
is accessible to every @Binds
method, @Provides
method and @Inject
constructor in this subcomponent.
Inside LoginCommand
, in the method handleArg()
, userCommandsRouterFactory.create()
is called with Account
parameter.
Since, DepositCommand
and WithdrawCommand
are bound in UserCommandsModule
, they get injected with the same Account
passed in LoginCommand
in their constructors.
Here, Account
is valid only inside the subcomponent.
So, UserAccountsModule
should be removed from the module list of CommandProcessorFactory
as there is no Account
instance it has access to, which it can inject into DepositCommand
and WithdrawCommand
.
This is mentioned in the tutorial:
The @Subcomponent annotation defines what modules Dagger should know about when creating instances for this subcomponent only. Just like @Component, it can take a list of modules: we’ve moved the UserCommandsModule that declares our DepositCommand here.
Ref: Dagger Tutorial 10 - Only allow depositing after logging in
So, removing UserCommandsModule.class
from CommandProcessorFactory
module list should fix the problem.
@Singleton
@Component(modules = {
LoginCommandModule.class,
HelloWorldModule.class,
SystemOutModule.class,
<Remove>UserCommandsModule.class,</Remove>
UserCommandsRouter.InstallationModule.class,
AmountsModule.class
})
public interface CommandProcessorFactory {
CommandProcessor commandProcessor();
}
Upvotes: 0