Dagger 2 ContributesAndroidInjector provide activity to module

Valentin :

I'm trying to figure out a less-boilerplate-y way to implement an ActivityModule that is used in all of my app activities. This is my current setup:

ActivityModule:

@Module
class ActivityModule(private val activity: Activity) {

    @Provides @ActivityScope
    fun providesActivity(): Activity = activity

    @Provides @ActivityContext @ActivityScope
    fun providesContext(): Context = activity

    @Provides @ActivityContext @ActivityScope
    fun providesLayoutInflater(): LayoutInflater = activity.layoutInflater

    @Provides @ActivityContext @ActivityScope
    fun providesResources(): Resources = activity.resources

}

AppActivityModule(provides activities for AndroidInjectionModule)

@Module(subcomponents = [
        AppActivityModule.WelcomeActivityComponent::class
    ])
    internal abstract class AppActivityModule {

        @Binds 
        @IntoMap 
        @ActivityKey(WelcomeActivity::class)
        abstract fun bindWelcomeActivityInjectorFactory(builder: WelcomeActivityComponent.Builder): AndroidInjector.Factory<out Activity>

        @ActivityScope
        @Subcomponent(modules = [(ActivityModule::class)])
        interface WelcomeActivityComponent : AndroidInjector<WelcomeActivity> {
        @Subcomponent.Builder abstract class Builder : AndroidInjector.Builder<WelcomeActivity>() {
            abstract fun activityModule(myActivityModule: ActivityModule): AndroidInjector.Builder<WelcomeActivity>

            override fun seedInstance(instance: WelcomeActivity) {
                activityModule(ActivityModule(instance))
            }
        }
    }
}

What I want AppActivityModule to be instead is:

@Module
internal abstract class AppActivityModule {
    @ContributesAndroidInjector(modules = [(ActivityModule::class)])
    abstract fun contributeWelcomeActivityInjector(): WelcomeActivity
}

But this, quite understandbly, gives me an error /di/AppActivityModule_ContributeWelcomeActivityInjector.java:29: error: @Subcomponent.Builder is missing setters for required modules or subcomponents: [...di.modules.ActivityModule]

My question is - is there a less boilerplate-y way to achieve what I'm trying to do? I know about @Bind and @BindsInstance (from this answer) but this seems to only work if I have a module-per activity and bind the concrete activity type which I don't want in this case - I want ActivityModule to work with all activities.

Jeff Bowman :

One way to minimize the boilerplate is to make a generic ActivityModule and then create a small specific Module per Activity. Forgive my Kotlin inexperience, but here goes:

// Abstract class so you don't have to provide an instance
@Module
abstract class ActivityModule {

    // No need for ActivityScope: You're always binding to the same Activity, so
    // there's no reason to have Dagger save your Context instance in a Provider.
    @Binds @ActivityContext
    abstract fun providesContext(activity: Activity): Context

    // This doesn't *have* to be in a companion object, but that way
    // Android can do a static dispatch instead of a virtual method dispatch.
    // If you don't need that, just skip the constructor arguments and make these
    // normal methods and you'll be good to go.
    @Module
    companion object {
        @JvmStatic @Provides @ActivityContext
        fun providesLayoutInflater(activity: Activity): LayoutInflater = 
            activity.layoutInflater

        @JvmStatic @Provides @ActivityContext
        fun providesResources(activity: Activity): Resources = activity.resources
    }
}

And your module:

@Module
internal abstract class AppActivityModule {

    @Module
    internal interface WelcomeActivityModule {
      // The component that @ContributesAndroidInjector generates will bind
      // your WelcomeActivity, but not your Activity. So just connect the two,
      // and suddenly you'll have access via injections of Activity.
      @Binds fun bindWelcomeActivity(activity: WelcomeActivity) : Activity
    }

    @ContributesAndroidInjector(
        modules = [ActivityModule::class, WelcomeActivityModule::class])
    abstract fun contributeWelcomeActivityInjector(): WelcomeActivity
}

Note that though this works for Activity, Service, BroadcastReceiver, and others, you might not want to be so quick about it for Fragment. This is because dagger.android handles fragment hierarchies with parent fragments, so from within a child component you might have access to YourApplication, YourActivity, YourParentFragment, and YourChildFragment, and all of their components. If something in YourChildFragmentComponent depends on an unqualified Fragment, it would be ambiguous whether it really wants YourParentFragment or YourChildFragment. That said, this design does make sense for Activities and certain Fragments, so it makes sense to use it (cautiously).


EDIT: What is @ActivityContext doing here?

@ActivityContext here is a qualifier annotation you'd define, which you can use to distinguish bindings of the same type in Dagger and other DI framework, presumably @ApplicationContext Context vs @ActivityContext Context. It would work to leave it out to try it, but I heavily recommend keeping it and avoiding binding an unqualified Context: Application and Activity contexts may be different, particularly in multi-screen or auto environments, and to get the right resources and data you should be precise about which you use. You can use this one as an example.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=438768&siteId=1