Reputation: 227
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
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