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();
}