Android | Dagger 2.根据条件

时间:2017-03-14 21:00:13

标签: android android-fragments dagger-2 android-mvp

我正在使用MVP和Dagger 2 DI。我有一个片段,我在一些活动中重用。作为片段的属性,我有一个接口类型作为片段的属性,比如MVPPresenter。根据片段的使用活动,我需要向其中注入不同的演示者(每个演示者都是MVPPresenter的实现)。所以我需要一种方法将MVPPresenter的每个实现注入到我需要的片段中。

目前,我有一个可怕的解决方案,它有效,但它完全错误,并创建了从未使用过的不必要的对象。这是代码:

public class MyFragment {

...

@Inject
public void setPresenter(@NonNull ProfilePresenter presenter) {
    if (mAdapter instanceof ProfileAdapter) {
        this.presenter = presenter;
    }
}

@Inject
public void setPresenter(@NonNull ContactsPresenter presenter) {
    if (mAdapter instanceof ContactsAdapter) {
        this.presenter = presenter;
    }
}
...
}

这是我的模块:

@Module
class PresentersModule {

@Provides
@Singleton
ProfilePresenter ProfilePresenter() {
    return new ProfilePresenter();
}

@Provides
@Singleton
ContactsPresenter ContactsPresenter() {
    return new ContactsPresenter();
}
}

您会看到,根据适配器类型,我指定演示者,或者不指定演示者。我知道这是愚蠢的。问题是Dagger需要精确的类型来指定注入和接口类型不能工作。 处理此类案件的正确方法是什么?

2 个答案:

答案 0 :(得分:4)

正如我所看到的,你有三种不同重量的解决方案。

按现在的方式注入两个选项:如果您事先知道所有Fragment的用例,并且不再需要更改依赖关系图与单个课程相比,您可以使用与现在类似的方法轻松完成。我的变体使用了提供者which are bound automatically for any object in your graph,因此您不必不必要地创建整个对象树;此外,@ Inject方法可以采用任意参数列表,因此如果您选择,可以在一个方法中执行所有方法注入。

@Inject
public void setPresenter(
        @NonNull Provider<ContactsPresenter> contactsPresenterProvider,
        @NonNull Provider<ProfilePresenter> profilePresenterProvider) {
    if (mAdapter instanceof ContactsAdapter) {
        this.presenter = contactsPresenterProvider.get();
    } else if (mAdapter instanceof ProfileAdapter) {
        this.presenter = profilePresenterProvider.get();
    }
}

另外两个解决方案涉及多个组件:不是说&#34;有一种方法将我的图形绑定在一起&#34;,您有效地要求Dagger为您生成多个选项,这意味着您的图表可以有很大差异但保持一致。如果您以不同的方式为应用程序的不同部分重用对象,这种技术可能会更有用,例如,如果您有一个Profile部分和一个Contacts部分,每个部分使用一个共同的A注入一个共同的B注入一个共同的C注入一个< em>不同 D.为了始终如一地支持这样的两个深度图,子组件是一个更好的选择。

使用组件依赖关系:rst's answer中一样,您可以使用组件依赖关系来隔离您的片段。他们做了很好的解释,所以我在这里不再重复。但是,您应该知道组件依赖项只能使用在您依赖的组件上公开的绑定:即使Foo和Bar绑定在DiComponent上,您也无法从ProfileComponent或ContactsComponent访问它们除非您将Foo getFoo()Bar getBar()放在DiComponent上。 (也就是说,组件依赖关系也不一定是Dagger组件;它们可以是您自己实现的任意类型,也可以让Dagger为您实现。)

使用子组件:虽然首先提到了subcomponents,但我认为它们需要更多解释,特别是因为它们是最近发布的dagger.android功能的核心组件,并且因为片段和其他UI片段很难用组件依赖项提取 - 子组件隐式并自动从周围组件继承绑定,因此您不必在DiComponent上显式公开绑定。请参阅this SO question上的其他差异。

@Component
public interface DiComponent {
    ProfileComponent getProfileComponent();    // Dagger generates implementations
    ContactsComponent getContactsComponent();  // as part of DiComponent.
}

@Subcomponent(modules={ContactsModule.class})
public interface ContactsComponent {
    void inject(MyFragment myFragment);
}

@Module
public interface ContactsModule {
    @Binds MvpPresenter bindMvpPresenter(ContactsPresenter contactsPresenter);
}

@Subcomponent(modules={ProfileModule.class})
public interface ProfileComponent {
    void inject(MyFragment myFragment);
}

@Module
public interface ProfileModule {
    @Binds MvpPresenter bindMvpPresenter(ProfilePresenter profilePresenter);
}

在上面,根DiComponent没有MvpPresenter的绑定,所以它本身不能注入MyFragment。但是,ProfileComponent和ContactsComponent可以,并且每个将使用在相应模块中配置的不同图形(但是静默地继承DiComponent模块的公共绑定)。如果图表的变化程度不同,例如每个MvpPresenter使用相同的Validator但使用不同的ProfileValidationRule而不是ContactsValidationRule,则可以将ValidationRule绑定到不同模块中的那些不同类以获得不同的行为。

(为了完整起见,您通常还可以选择使用a factory like AutoFactory并将类似于演示者的参数传递到特定容器(如Fragment)。但是,如果您需要,这只是一个选项。在Android强制使用零参数公共构造函数时创建实例,而不是真正的选项,这样它就可以随意创建Fragment实例。)

答案 1 :(得分:1)

通过查看你给mvp-presenters的名字,可以得出结论,他们的互补mvp视图应该分开并以不同的片段实现。

但是如果你希望保持原样,只在你的片段中声明单个setPresenter方法,可能最简单的方法来处理你的问题将是引入具有补充模块的单独组件,以提供理想的演示者的实施方式。

要使此解决方案起作用,您需要调整片段以包含setPresenter方法的单个声明,并将MVPPresenter类型作为参数:

@Inject
public void setPresenter(@NonNull MVPPresenter presenter) {
    this.presenter = presenter;
}

之后,您需要提供公开inject(...)方法的组件并声明适当模块的使用。由于这些依赖图将依赖于 main 组件实例,因此它们应该获得自己的范围(与活动或片段相关联,具体取决于实际持有图形对象的类)。

例如,如果您使用DiComponent为所有依赖项提供通过@Singleton注释定义的范围,则需要声明@MyFragmentScope注释并提供组件,具体取决于上述内容 - 提到DiComponent,以宣布可注射的演示者:

import javax.inject.Scope;

@Scope
public @interface MyFragmentScope {
}

您的依赖组件如下所示:

@MyFragmentScope
@Component(dependencies = DiComponent.class, modules = ProfileModule.class)
public interface ProfileComponent {
    void inject(MyFragment fragment);
}

有补充模块:

@Module
public class ProfileModule {
    @Provides
    @MyFragmentScope
    MVPPresenter providesProfilePresenter() {
        return new ProfilePresenter();
    }
}

注意:返回类型为MVPPresenter,而非具体实现。

同样,您需要为ContactsComponent创建ContactsModuleContactsPresenter

最终,您应该使用适当的组件实例来执行注入。现在而不是使用

diComponent.inject(myFragment)

你应该使用能提供所需依赖性的组件。

此时你实际上有一个开关定义应该使用哪个演示者。 如果ProfilePresenter注射你需要使用:

DaggerProfileComponent.builder()
        .diComponent(diComponent)
        .build()
        .inject(myFragment);

如果ContactsPresenter注射你需要使用:

DaggerContactsComponent.builder()
        .diComponent(diComponent)
        .build()
        .inject(myFragment);

对于像活动这样的应用程序的较小部分使用单独的组件是相当普遍的做法。可以将这些组件声明为常规依赖组件或子组件(请参阅@Subcomponent文档以供参考)。从Dagger 2.7开始,有一种通过@Module.subcomponents声明子组件的新方法。由于这个事实,有机会将AppComponent与活动子组件分离。您可以参考frogermcs的sample GitHub repository作为参考。他在这个主题上也有很好的补充blog post