Reputation: 498
I came across an instance where Dagger-2 wouldn't let me lazy inject. It seems that it still requires me to supply the object at compile time. Why is that?
The stacktrace:
[Dagger/MissingBinding] @javax.inject.Named("htfModel") de.wimj.core.Applications.IModel cannot be provided without an @Provides-annotated method.
[ERROR] @javax.inject.Named("htfModel") de.wimj.core.Applications.IModel is injected at
[ERROR] de.wimj.ui.Mt5Painter.<init>(…, htfTradeModel, …)
[ERROR] dagger.Lazy<de.wimj.ui.Mt5Painter> is injected at
[ERROR] de.wimj.core.Applications.ModelMqlBased.<init>(…, mt5Painter, …)
[ERROR] dagger.Lazy<de.wimj.core.Applications.ModelMqlBased> is injected at
[ERROR] de.wimj.di.components.trademodel.ModelModule.iModel(modelMqlBased, …)
[ERROR] de.wimj.core.Applications.IModel is provided at
[ERROR] de.wimj.di.components.trademodel.ModelComponent.createModel()
The code for the stacktrace:
//Got it, Dagger-2 wants me to provide a IModel here
@ModelScope
@Component(modules = { ModelModule.class }, dependencies = { ClientComponent.class })
public interface ModelComponent {
IModel createModel();
@Component.Builder
interface Builder {
ModelComponent build();
Builder clientComponent(ClientComponent clientComponent); //MT5Trader comes from this component
}
}
//At this point I will provide the IModel. I do NOT get, why Dagger-2 forces
//me to provide a "ModelMqlBased" though. I obviously lazy-inject it.
//I used this pattern in other cases as well (providing an interface and
//lazy-injecting the possible instantiations as params)
@Module
public class ModelModule {
@Provides
@ModelScope
IModel iModel( Lazy<ModelMqlBased> modelMqlBased, //lazy-injection here!
ModelFileBased modelFileBased,
@Named("configClientType")String clientType) {
switch (clientType) {
case "mqlBot":
return modelMqlBased.get();
case "fileBot":
return modelFileBased;
default:
throw new RuntimeException();
}
}
}
The following code should be irrelevant (the crux is the ModelModule), but for the sake of completion:
@ModelScope
public class ModelMqlBased implements IModel {
@Inject
public ModelMqlBased( Lazy<Mt5Painter> mt5Painter) {
super();
this.mt5Painter = mt5Painter.get();
}
}
//this one sits in a "higher-scoped" component
@ClientScope
public class Mt5Painter {
private IModel htfModel;
private IModel ltfModel;
@Inject
public Mt5Painter(@Named("htfModel") Lazy<IModel> htfTradeModel, @Named("ltfModel") Lazy<IModel> ltfTradeModel) {
super();
this.htfModel = htfTradeModel.get();
this.ltfModel = ltfTradeModel.get();
}
Upvotes: 0
Views: 2764
Reputation: 95634
Lazy<T>
doesn't mean "figure out later whether T is bound", it means "ensure a binding exists for T at compile time, but only create an instance at runtime after I call get
". You will still need to make the binding for T available in all cases, but Dagger won't try to create an instance of it until you explicitly ask for it.
Dagger requires that for all uses of Provider<T>
and Lazy<T>
, the binding T needs to exist at compile time, even if at runtime you do not call it. This ensures that if you do call get()
on the Provider or Lazy instance, it does not fail at runtime for a binding it knew was missing at compile time. (Lazy behaves exactly as Provider does, except Lazy remembers the instance it returns regardless of whether the binding was scoped.)
This means that one of your options is to add a binding for ModelMqlBased that returns null
or throws an Exception, which normally would be a terrible idea in Dagger, but it would be sufficient for a case where you know at runtime that the Provides method is never called.
A different way to achieve the flexibility you're looking for is with @BindsOptionalOf. This allows you to inject an Optional<T>
or Optional<Lazy<T>>
, which resolves to a present value if the binding exists and an absent placeholder if the binding does not.
@Module
public abstract class ModelModule {
// Note abstract class and static/abstract methods.
@BindsOptionalOf
abstract ModelMqlBased bindOptionalOfModelMqlBased();
@Provides
@ModelScope
static IModel iModel(Optional<ModelMqlBased> modelMqlBased,
ModelFileBased modelFileBased,
@Named("configClientType")String clientType) {
switch (clientType) {
case "mqlBot":
return modelMqlBased.get();
case "fileBot":
return modelFileBased;
default:
throw new RuntimeException();
}
}
}
This may make it easier to reuse modules, especially because (like multibindings) you can supply as many @BindsOptionalOf abstract T bindOptionalOfT();
methods as you'd like and Dagger will not complain about the duplication.
Upvotes: 2