Mohammed Daoud
Mohammed Daoud

Reputation: 227

Default implementation using dagger if @Named not found

The problem I am facing is that I have a base class and multiple child class. For resolving the particular child class I am using @Named annotation in Dagger 2. What I am trying to achieve is if I Inject with @Named("Child3") and there is not Provide with @Named("Child3") then I should get instance of Base class by default.

public class BaseClass {

    public void haveFun(){
        System.out.print("Having fun base");
    }

}

public class Child1 extends BaseClass {
    @Override
    public void haveFun() {
        System.out.print("Having fun Child1");
    }
}

public class Child2 extends BaseClass {
    @Override
    public void haveFun() {
        System.out.print("Having fun Child2");
    }
}

Now in the module I am providing the objects like this:

@Provides
@Named("Child1")
static BaseClass provideChild1(){
    return new Child1();
}

@Provides
@Named("Child2")
static BaseClass provideChild2(){
    return new Child2();
}

@Provides
static BaseClass provideBaseClass(){
    return new BaseClass();
}

Now in my activity I am injecting like this:

public class ReceiptActivity extends AppCompatActivity {

    @Inject @Named("Child1") BaseClass child1;
    @Inject @Named("Child2") BaseClass child2;
    @Inject @Named("Child3") BaseClass child3;

    // ...

}

As @Named("Child3") is not provided there is a compile time error, but what I want is if @Named("Child3") is not there I should get a BaseClass instance.

How can I achieve this?

Upvotes: 3

Views: 2789

Answers (1)

Jeff Bowman
Jeff Bowman

Reputation: 95704

Unfortunately, qualified bindings (bindings using qualifier annotations like @Named) don't really have a fallback or default. Each binding is different, and the different bindings aren't considered to be related. This is also the same for bindings lacking any sort of qualifier: @Named("Child3") BaseClass and BaseClass are completely different bindings to Dagger.

This makes sense, too: @Named("Porsche") Engine and @Named("Lawnmower") Engine are never really going to substitute for one another, despite sharing a base type. They're entirely different dependencies, and if you're missing a @Named("Porsche") Engine, Dagger follows the policy that it should fail at compile time rather than scrounging around for a mismatched or unqualified Engine.

If this is known at compile time, you can bind your own default:

@Binds @Named("Child3") BaseClass bindChild3(BaseClass baseClass);

// or the reverse, if BaseClass weren't bound and you wanted it to default
// to @Named("Child1") BaseClass

@Binds BaseClass bindBaseClass(@Named("Child1") BaseClass child1);

You could also bind a Map or use Multibindings to indicate the substitutability or flexibility you're looking for. Rather than injecting the binding itself, you'd inject a Map, or inject a Factory that encapsulates the map and pulls out the right binding for you.

// This uses Multibindings, but you could manually create a Map instead.

@Binds @IntoMap @StringKey("Child1")
abstract BaseClass provideChild1(Child1 child1);

@Binds @IntoMap @StringKey("Child2")
abstract BaseClass provideChild2(Child2 child2);

// Then in your consumer...

@Inject Map<String, BaseClass> mapOfBaseClasses;
@Inject BaseClass baseClass;

// Or make an injectable Factory:

public class YourClassFactory {
  private final Map<String, Provider<BaseClass>> baseClassMap;
  private final Provider<BaseClass> baseClassProvider;

  @Inject public YourClassFactory(/* ... */) { /* set fields */ }

  public BaseClass get(String key) { /* write fallback logic here */ }
}

If you have a specific binding that may be present or absent, you can also use @BindsOptionalOf to indicate that the binding is allowed to be missing at compile time, and then you can detect it at runtime.

@BindsOptionalOf @Named("Child3")
abstract BaseClass provideOptionalOfChild3();

// Then in your consumer:

private final BaseClass baseClass;

@Inject public YourConsumer(
    @Named("Child3") Optional<BaseClass> optionalChild3,
    Provider<BaseClass> defaultBaseClass) {
  baseClass =
      optionalChild3.isPresent()
      ? optionalChild3.get()
      : defaultBaseClass.get();
}

Upvotes: 4

Related Questions