Dagger2: как не дублировать модуль-компонент для активности / фрагментов, полагаясь на одну базу (модуль / компонент) для базы (действие / фрагмент) и

Заданный BaseFragment и его подклассы: DerivedFragmentA, DerivedFragmentB, ...

Допустим, что большинство @Inject поля являются общими для каждого фрагмента и поэтому объявлены в BaseFragment:

abstract class BaseFragment : DaggerFragment() {
    @Inject lateinit var vmFactory: ViewModelProvider.Factory
}

class DerivedFragmentA : BaseFragment()
class DerivedFragmentB : BaseFragment()
...

Для каждого из производных фрагментов мы должны вручную создать пары Модуль-Компонент, такие как:

@Subcomponent
interface DerivedFragmentAComponent : AndroidInjector<DerivedFragmentA> {

    @Subcomponent.Builder
    abstract class Builder : AndroidInjector.Builder<DerivedFragmentA>()
}

@Module(subcomponents = [DerivedFragmentAComponent::class])
abstract class DerivedFragmentAModule {

    @Binds @IntoMap @FragmentKey(DerivedFragmentA::class)
    abstract fun bind(builder: DerivedFragmentAComponent.Builder): AndroidInjector.Factory<out Fragment>
}

И установите каждый из них на некоторый внешний компонент, например так:

@Subcomponent(modules = [DerivedFragmentAModule::class, DerivedFragmentBModule::class, ...])
interface MainComponent : AndroidInjector<MainActivity> {

    @Subcomponent.Builder
    abstract class Builder : AndroidInjector.Builder<MainActivity>()
}

Но это своего рода шаблон.

Если мы попытаемся сделать только один Module-Component для BaseFragment и установить только его в MainComponent - мы получим исключение во время выполнения при вызове AndroidInjector.inject(fragment) метод, со следующим сообщением:

  "No injector factory bound for Class<DerivedFragmentA>. Injector factories were bound for supertypes of DerivedFragmentA: BaseFragment. Did you mean to bind an injector factory for the subtype?"

Есть ли способ исправить это и избежать дублирования кода? Или, поскольку Dagger-2 сильно зависит от имен классов, это невозможно?

1 ответ

Инъекция с Dagger 2 всегда работает с указанным вами типом. inject(fragment : BaseFragment) будет только вводить поля BaseFragment и ни одно из полей, объявленных в каких-либо подклассах. Это просто то, что вы должны иметь в виду.

Вы говорите, что хотели бы просто объявить один компонент и вставить вещи в BaseFragment только, так что это именно то, что вы можете сделать. Вместо создания подкомпонента для вашего DerivedFragment Вы создаете один для вашего BaseFragment...

@Subcomponent
interface BaseFragmentComponent : AndroidInjector<BaseFragment> {

    @Subcomponent.Builder
    abstract class Builder : AndroidInjector.Builder<BaseFragment>()
}

Тогда вы можете связать BaseFragment.Builder на ваш DerivedFragmentX s.

@Module(subcomponents = [BaseFragmentComponent::class])
abstract class BaseFragmentModule {

    @Binds @IntoMap @FragmentKey(DerivedFragmentA::class)
    abstract fun bind(builder: BaseFragmentComponent.Builder): AndroidInjector.Factory<out Fragment>

    @Binds @IntoMap @FragmentKey(DerivedFragmentB::class)
    abstract fun bind(builder: BaseFragmentComponent.Builder): AndroidInjector.Factory<out Fragment>

    @Binds @IntoMap @FragmentKey(DerivedFragmentC::class)
    abstract fun bind(builder: BaseFragmentComponent.Builder): AndroidInjector.Factory<out Fragment>
}

Важный бит должен установить @FragmentKey(DerivedFragmentA::class) ссылаться на подкласс, так как это тот, который Dagger будет искать при вызове AndroidInjection.inject(fragment),


Я по-прежнему рекомендую вам не использовать этот подход, так как в итоге вы получите сочетание некоторых фрагментов, которые будут полностью введены, и других, где это просто BaseFragment. Это звучит странно для меня.

Вы могли бы просто использовать @ContributesAndroidInjector вместо этого, чтобы сгенерировать шаблонный код для вас и правильно внедрить каждый фрагмент.

Другие вопросы по тегам