Android | Dagger 2.根据条件将不同的子类注入Fragment

Android | Dagger 2. Injecting different subclasses into Fragment depending of a condition

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

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

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 需要准确的类型来指定注入,而接口类型将无法工作。 处理此类情况的正确方法是什么?

查看您为 mvp 演示者提供的名称,可以得出结论,他们的互补 mvp 视图应该分开并在不同的片段中实现。

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

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

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

之后,您需要提供公开 inject(...) 方法并声明使用适当模块的组件。由于这些依赖图将依赖于 main 组件实例,因此它们应该有自己的范围(绑定到 activity 或片段,具体取决于 class 实际持有的内容图形对象)。

例如,如果您使用 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();
    }
}

注:return类型为MVPPresenter,不是具体实现。

同样,您需要为 ContactsPresenter 创建 ContactsComponentContactsModule

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

diComponent.inject(myFragment)

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

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

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

或者在 ContactsPresenter 注入的情况下,您需要使用:

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

为应用程序的较小部分(如活动)使用单独的组件是相当普遍的做法。可以将此类组件声明为常规依赖组件或子组件(请参阅 @Subcomponent 文档以供参考)。从关于此主题的 Dagger 2.7 there is a new way of declaring Subcomponents via @Module.subcomponents. Due to this fact there's an opportunity to decouple AppComponent from Activities Subcomponents. You may refer to sample GitHub repository from frogermcs for reference. He also has a great complementary blog post 开始。

如我所见,您有三个不同权重的解决方案。

像现在一样注入两个选择:如果您预先知道 Fragment 的所有用例,并且不需要改变依赖关系图在单个 class 上,您可以使用与现在类似的方法轻松完成此操作。我的变体使用提供者 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();
    }
}

其他两个解决方案涉及多个组件:您不是说 "there is one way of binding my graph together",而是在有效地要求 Dagger 为您生成多个选项,这意味着您的图表可以有很大差异但保持一致。如果您以不同的方式为应用程序的不同部分重用对象,则此技术可能更有用,例如,如果您有一个个人资料部分和一个联系人部分,每个部分都使用公共 A 注入公共 B 注入公共 C 注入 不同 D. 为了始终如一地支持这样的两个深度图,子组件是一个更好的选择。

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

使用子组件: 虽然首先提到了 subcomponents, I think they warrant a bit more explaining, particularly because they are a core component of the recently-released dagger.android functionality, and because Fragments and other UI pieces can be difficult to extract with component dependencies—subcomponents implicitly and automatically inherit bindings from the surrounding component, so you don't have to explicitly expose bindings on your DiComponent. See other differences at

@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 使用相同的验证器但具有不同的 ProfileValidationRule 与 ContactsValidationRule,您可以将 ValidationRule 绑定到不同模块中的那些不同的 classes 以获得不同的行为。

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