Given BaseFragment and its subclasses: DerivedFragmentA, DerivedFragmentB, ...
Let's say, that most of @Inject
fields are common for each fragment and hence declared in BaseFragment:
abstract class BaseFragment : DaggerFragment() {
@Inject lateinit var vmFactory: ViewModelProvider.Factory
}
class DerivedFragmentA : BaseFragment()
class DerivedFragmentB : BaseFragment()
...
For each of derived fragments we have to manually create Module-Component pairs, such as:
@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>
}
And install each of them to some outer component, like that:
@Subcomponent(modules = [DerivedFragmentAModule::class, DerivedFragmentBModule::class, ...])
interface MainComponent : AndroidInjector<MainActivity> {
@Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<MainActivity>()
}
But this is kind of boilerplate.
If we try to make just a single Module-Component for BaseFragment and install only it to the MainComponent - we will get a runtime exception during invocation of AndroidInjector.inject(fragment)
method, with the following message:
"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?"
Is there any way to fix that and avoid code duplication? Or as Dagger-2 strongly relies on classnames, its impossible ?
Injection with Dagger 2 always works with the type you specify. inject(fragment : BaseFragment)
will only ever inject the fields of BaseFragment
and none of the fields declared in any subclasses. That's just something you have to keep in mind.
You say you would like to just declare one component and inject things into the BaseFragment
only, so that's exactly what you can do. Instead of creating a subcomponent for your DerivedFragment
you create one for your BaseFragment
...
@Subcomponent
interface BaseFragmentComponent : AndroidInjector<BaseFragment> {
@Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<BaseFragment>()
}
Then you can bind the BaseFragment.Builder
to your 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>
}
The important bit is to set @FragmentKey(DerivedFragmentA::class)
to reference the subclass, since that's the one Dagger will look up when calling AndroidInjection.inject(fragment)
.
I'd still recommend you not to use this approach, as you'd end up with a mix of some fragments being fully injected and others where it's just the BaseFragment. This sounds confusing to me.
You could just use @ContributesAndroidInjector
instead to generate the boilerplate code for you and properly inject every fragment.