I'm using dagger-android for DI. There're a few common dependencies I want to provide to every Activity. They depend on an Activity class but don't relay on a certain Activity implementation. Like this:
class NavigatorImpl(private val activity: Activity) : Navigator {..}
Now I have to write the same code at every ActivityModule:
@Module
class SomeActivityModule {
@Provides
fun navigator(activity: SomeActivity): Navigator = NavigatorImpl(activity)
// some deps...
}
@Module
class AnotherActivityModule {
@Provides
fun navigator(activity: AnotherActivity): Navigator = NavigatorImpl(activity)
// another deps...
}
I tried to provide these dependencies via AndroidSupportInjectionModule
but it turns out that Dagger generates separate Submodules for each Activity, and we can access to an Activity dependency only at the certain modules (SomeActivityModule
and AnotherActivityModule
at this example):
@Module(includes = [AndroidSupportInjectionModule::class])
interface AppInjectionModule {
@PerActivity
@ContributesAndroidInjector(modules = [SomeActivityModule::class])
fun someActivity(): SomeActivity
@PerActivity
@ContributesAndroidInjector(modules = [AnotherActivityModule::class])
fun anotherActivity(): AnotherActivity
// we don't have any Activity in the graph -> throws an error
@PerActivity
@Provides
fun navigator(navigator: NavigatorImpl): Navigator
}
So how can I provide such dependencies without duplicating code in every ActivityModule?
First of all you put all of your common things into a module that you can reuse. (Also please use constructor injection with @Binds
as shown below
class Navigator @Inject constructor(activity : Activity) { /* ... */ }
@Module
interface BaseActivityModule {
@Binds
fun navigator(navigator: NavigatorImpl): Navigator
// other bindings...
}
Then you have to switch to custom subcomponent declarations instead of @ContributesAndroidInjector
. There is a related open issue, but for now you have to write the boilerplate.
You declare an AndroidInjector.Factory
that binds everything you need, then you use this for your subcomponent builder instead of the default AndroidInjector.Builder
.
object MyCustomInjector {
abstract class ActivityBuilder<T : Activity> : AndroidInjector.Factory<T> {
override fun create(instance: T): AndroidInjector<T> {
seedInstance(instance)
activity(instance)
return build()
}
@BindsInstance
abstract fun seedInstance(instance: T)
@BindsInstance
abstract fun activity(instance: Activity)
abstract fun build(): AndroidInjector<T>
}
}
@Module(subcomponents = [(SomeActivityModule.SomeActivitySubcomponent::class)])
abstract class SomeActivityModule {
@Binds
@IntoMap
@ActivityKey(SomeActivity::class)
internal abstract fun contributeSomeActivity(builder: SomeActivitySubcomponent.Builder): AndroidInjector.Factory<out Activity>
@PerActivity
@Subcomponent(modules = [BaseActivityModule::class])
interface SomeActivityActivitySubcomponent : AndroidInjector<SomeActivity> {
@Subcomponent.Builder
abstract class Builder : MyCustomInjector.ActivityBuilder<SomeActivity>()
}
}
Your subcomponent builder thus binds both SomeActivity
and Activity
, and your subcomponent includes the BaseActivityModule
with the bindings defined above.
For more information you can also have a look at this related question with a few more steps...