Как лениво вводить интерфейсы с Dagger-2? - PullRequest
0 голосов
/ 08 апреля 2019

Я наткнулся на случай, когда Dagger-2 не позволил бы мне лениво вводить.Кажется, что все еще требуется, чтобы я предоставил объект во время компиляции.Почему это так?

Трассировка стека:

[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()

Код для трассировки стека:

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

Следующий код не имеет значения (суть - ModelModule), но для завершения:

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

1 Ответ

1 голос
/ 09 апреля 2019

Lazy<T> не означает «выяснить позже, связан ли T», это означает «обеспечить привязку для T во время компиляции, но создавать экземпляр только во время выполнения после того, как я вызову get».Вам все равно нужно будет сделать привязку для T доступной во всех случаях, но Dagger не будет пытаться создать ее экземпляр, пока вы не попросите об этом явно.

Dagger требует, чтобы она использовалась для всех случаев использования Provider<T>и Lazy<T>, привязка T должна существовать во время компиляции, даже если во время выполнения вы ее не вызываете.Это гарантирует, что если вы вызовете get() для экземпляра Provider или Lazy, он не завершится с ошибкой во время выполнения для той привязки, которая, как он знал, отсутствовала во время компиляции.(Lazy ведет себя точно так же, как и Provider, за исключением того, что Lazy запоминает экземпляр, который он возвращает, независимо от того, была ли привязка ограничена.)

Это означает, что один из ваших вариантов - добавить привязку для ModelMqlBased, которую возвращает null или выдает исключение , что обычно было бы ужасной идеей в Dagger, но этого было бы достаточно для случая, когда во время выполнения вы знаете, что метод Provides никогда не вызывается.

Другой способдостичь желаемой гибкости можно с помощью @ BindsOptionalOf .Это позволяет вам вводить Optional<T> или Optional<Lazy<T>>, который разрешает текущее значение, если привязка существует, и отсутствующий заполнитель, если привязка не существует.

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

Это может упростить повторное использованиемодулей, особенно потому, что (например, мультибиндинги) вы можете предоставить столько методов @BindsOptionalOf abstract T bindOptionalOfT();, сколько захотите, и Dagger не будет жаловаться на дублирование.

...