0

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();
    }
cobby
  • 484
  • 3
  • 18

1 Answers1

1

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.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • And how do I autowire `ModelMqlBased` when using `@BindsOptionalOf`? Do I need to manually wire it together inside the method. Obviously modelMqlBased.get() will always result in a NoSuchElementException, as there will never be a value, right? – cobby Apr 10 '19 at 18:42
  • @cobby `@BindsOptionalOf` is best for when you have a reusable module where the binding in question may or may not exist in some other module. If you only know at runtime whether the binding is necessary, then Dagger is going to need a `@Provides` method in all cases, because it won't let you ask for a binding it doesn't know how to provide. Injecting `Lazy` counts as asking for the binding T, even if you only ask for an instance of T much later. – Jeff Bowman Apr 10 '19 at 18:44
  • Hmm, then it seems that neither `Lazy` nor `BindsOptionalOf` may solve my problem. In my case I need to create a `ModelMqlBased` or `ModelFileBased` based on a config flag. The `ModelModule` should be used in two components, one that provides the dependencies for ModelMqlBased, one for ModelFileBased (via component dependencies). I thought it would be nice to use one Module and return the required instantiation based on that config flag. Obviously I need to split the module up for each component. Is that right? – cobby Apr 10 '19 at 19:14
  • 1
    If you've committed to a component-dependency-based structure, then I think that's right. I'd still favor the "Inject the dependencies" solution on [my other answer](https://stackoverflow.com/a/48893844/1426891), which I saw you comment on. That style of solution would allow you to choose at runtime without the overhead of component dependencies, and without creating unnecessary instances, but you would have to configure your Component to be able to provide both ModelMqlBased and ModelFileBased. All this does stray from your original question about `Lazy` here, though. – Jeff Bowman Apr 10 '19 at 19:19
  • Which solution is that on your post, the one under the heading "Component dependencies"? – cobby Apr 10 '19 at 19:44
  • @cobby It's the one titled "Inject the configuration" (sorry, not "Inject the dependencies"). Very much resembles yours, except uses Provider where you have Lazy. As long as your deployed binary contains both implementations, I'm not sure the component dependencies are going to buy you much flexibility or performance unless the classloading required for one of the bindings is substantial. – Jeff Bowman Apr 10 '19 at 21:26
  • I see, thank you Jeff! However, Provider behaves like Lazy with regards to the problem mentioned in my OP. I would have to provide all dependencies for `ModelMqlBased` which again, I don't want to. – cobby Apr 10 '19 at 22:38