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
вместо этого, чтобы сгенерировать шаблонный код для вас и правильно внедрить каждый фрагмент.