Андроид / Кинжал 2. Введение различных подклассов во фрагмент в зависимости от состояния


Я работаю с MVP и Dagger 2 DI. У меня есть фрагмент, который я повторно использую в нескольких действиях. У меня есть тип интерфейса для presenter как свойство фрагмента, скажем 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();
}
}

Видите ли, в зависимости от типа адаптера, я назначаю presenter или нет. Я знаю, что это глупо и все такое. Проблема в том, что Dagger нуждается в точном типе для инъекции, чтобы быть определенным, и тип интерфейса не будет работать. Что такое правильный способ борьбы с такими случаями?

2 3

2 ответа:

Просматривая имена, которые вы дали MVP-докладчикам, можно сделать вывод, что их взаимодополняющие MVP-представления скорее должны быть разделены и реализованы в разных фрагментах.

Но если вы хотите сохранить все как есть, имея только один метод setPresenter, объявленный в вашем фрагменте, вероятно, самый простой способ решить вашу проблему-это ввести отдельные компоненты с дополнительными модулями для обеспечения желательных реализаций presenter.

Для этого решения для работы вам нужно будет настроить свой фрагмент так, чтобы он содержал одно объявление метода setPresenter с типом MVPPresenter в качестве аргумента:

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

После этого вам нужно будет предоставить компоненты, раскрывающие метод inject(...) и объявляющие использование соответствующего модуля. Поскольку эти графики зависимостей будут зависеть от экземпляра компонентаmain , они должны получить свою собственную область видимости (привязанную к действию или фрагменту, в зависимости от того, какой класс фактически содержит объект graph).

Например, если вы использовали 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 и ContactsModule для вашего ContactsPresenter.

В конечном итоге вы должны использовать соответствующий экземпляр компонента для выполнения инъекции. Теперь вместо использования

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. Благодаря этому есть возможность отделить компонент App от субкомпонентов Activities. Вы можете обратиться к образцу репозитория GitHub из frogermcs для справки. У него также есть отличный дополнительный пост в блогена эту тему.

У вас есть, как я вижу, три решения разной степени тяжести.

Если вы заранее знаете все варианты использования вашего фрагмента, и вам не нужно изменять графики зависимостей больше, чем на одном классе, вы можете сделать это легко, используя аналогичный метод, который у вас есть сейчас. В моем варианте используются провайдеры , которые автоматически привязываются к любому объекту в вашем графе , чтобы вы не создавали без необходимости целые деревья объектов; кроме того, @ 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();
    }
}
Два других решения включают в себя несколько компонентов: вместо того, чтобы сказать: "есть один способ связать мой граф вместе", вы фактически просите Dagger создать несколько вариантов для вас, что означает, что ваши графики могут сильно отличаться, но оставаться последовательными. Этот метод может быть более полезным, если вы повторно используете объекты разными способами для разных целей. разделы вашего приложения, например, если у вас есть раздел профиля и раздел контактов, каждый из которых использует общий a, вводящий общий B, вводящий общий C, вводящий другой D. Для последовательной поддержки двух глубоких графов, таких как этот, дочерние компоненты являются гораздо лучшим вариантом.

Используйте зависимости компонентов: как и в ответе rst, вы можете использовать зависимости компонентов для изоляции ваших фрагментов. Они довольно хорошо объяснили, так что я не буду повторяться. это здесь. Однако следует помнить, что зависимости компонентов могут использовать только те привязки, которые доступны для компонента, от которого вы зависите: даже если Foo и Bar связаны с DiComponent, вы не сможете получить к ним доступ из вашего ProfileComponent или ContactsComponent, если вы не поместите Foo getFoo() и Bar getBar() на свой DiComponent. (Тем не менее, зависимости компонентов не обязательно должны быть компонентами Dagger; они могут быть произвольными типами, которые вы реализуете самостоятельно или позволяете реализовать Dagger для ты.)

Использование субкомпонентов: хотя впервые упоминаются субкомпоненты , я думаю, что они заслуживают немного большего объяснения, особенно потому, что они являются основным компонентом недавно выпущенного кинжала .функциональность android, а также потому, что фрагменты и другие части пользовательского интерфейса могут быть трудно извлечь с зависимостями компонентов-субкомпоненты неявно и автоматически наследуют привязки от окружающего компонента, поэтому вам не нужно явно выставлять привязки на вашем компьютере. Дикомпонент. Смотрите другие различия в этом так называемом вопросе.

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

В приведенном выше примере корневой Дикомпонент не имеет привязки к MvpPresenter, поэтому сам по себе он не может вводить MyFragment. Однако ProfileComponent и ContactsComponent могут, и каждый будет использовать различные графики, настроенные в соответствующих модулях (но молчаливо наследуя общие привязки от модулей DiComponent). Если графики изменяются по-разному дальше вниз, как с каждым MvpPresenter, использующим тот же валидатор но с другим ProfileValidationRule по сравнению с ContactsValidationRule, вы можете привязать ValidationRule к этим различным классам в ваших различных модулях, чтобы получить различное поведение.

(для полноты, вы обычно также можете использовать завод, как AutoFactory и передайте такой параметр, как presenter, в ваш конкретный контейнер, например Fragment. Тем не менее, это только действительно вариант, если вы создаете свои экземпляры, и не совсем вариант, когда Android заставляет открытый конструктор zero-arg, чтобы он мог создавать экземпляры фрагментов по своему желанию.)