Java – If no @Named is found, the default implementation of Dagger is used

If no @Named is found, the default implementation of Dagger is used… here is a solution to the problem.

If no @Named is found, the default implementation of Dagger is used

The problem I face is that I have a base class and multiple subclasses. To address specific subclasses, I use @Named annotations in Dagger 2. What I want to achieve is that if I inject @Named(“Child3”) and don’t provide a @Named(“Child3”) then I should get the base class instance 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 provide an object 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 inject like this:

public class ReceiptActivity extends AppCompatActivity {

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

// ...

}

Because @Named(“Child3”) doesn’t

provide compile-time errors, but what I want is that if @Named("Child3") doesn’t I should get a BaseClass instance.

How can I do this?

Solution

Unfortunately, qualified bindings (binds) (bindings annotated with qualifiers like @Named) actually have no fallback or default values. Each binding (bind) is different, and different binds are not considered related. The same is true for binds that lack any type qualifier: @Named("Child3") BaseClass and BaseClass are completely different binds to Dagger.

It also makes sense: @Named (“Porsche“) Engine and @Named("Lawnmower") Engine will never really replace each other, despite sharing a basic type. They are completely different dependencies, and if you are missing a @Named ("Porsche") engine, Dagger follows the strategy that it should fail at compile time instead of looking around for a mismatched or substandard engine.

If this is known at compile time, you can bind (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 can also bind maps or use Multibindings to indicate the fungibility or active you are looking for. Instead of injecting the bind itself, inject a map, or inject a wrapper map and extract the correct binding for you in the factory.

// 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 (bind) that may or may not exist, you can also use the > @BindsOptionalOf indicates that the binding (bind) is allowed to be lost 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();
}

Related Problems and Solutions