Dagger: ContributesAndroidInjector in a Subcomponent

Created on 30 Aug 2018  路  6Comments  路  Source: google/dagger

Is there a way to add ContributesAndroidInjector for activity in a Subcomponent? I am trying to create a scope that works across a few activities.

What I have is this:

AppComponent

@Component(modules = [
    AndroidInjectionModule::class,
    AndroidSupportInjectionModule::class
])
interface AppComponent: AndroidInjector<DaggerApplication> {
    override fun inject(instance: DaggerApplication)
    fun chatComponent(): ChatComponent.Builder
    fun inject(needle: Needle)

    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: Application): AppComponent.Builder
        fun build(): AppComponent
    }
}

ChatScope

@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class ChatScope

ChatComponent

@Subcomponent(modules = [ChatBindingModule::class])
@ChatScope
interface ChatComponent {

    @Subcomponent.Builder
    interface Builder {
        fun chatModule(chatModule: ChatModule): Builder
        fun build(): ChatComponent
    }

}

ChatBindingModule

@Module
abstract class ChatBindingModule {
    @ActivityScoped
    @ContributesAndroidInjector(modules = [])
    abstract fun chatListActivity(): ChatListActivity

    @ActivityScoped
    @ContributesAndroidInjector(modules = [])
    abstract fun chatDetailActivity(): ChatDetailActivity
}

With this code dagger fails to find injector for both activities. I guess I do know why that is happening but no idea how to solve this problem

Most helpful comment

You'd need to provide custom code in your Application instance that would know how to access ChatComponent. Something like this:

class MyApplication : DaggerApplication {
  override fun activityInjector(): AndroidInjector<Activity> {
      return object : AndroidInjector<Activity> {
        override fun inject(activity: Activity) {
            if (activity.isChatActivity()) { 
              component.chatComponent().activityInjector().inject(activity)
            } else {
              component.activityInjector().inject(activity)
         }
     }
  }
}

All 6 comments

You'd need to provide custom code in your Application instance that would know how to access ChatComponent. Something like this:

class MyApplication : DaggerApplication {
  override fun activityInjector(): AndroidInjector<Activity> {
      return object : AndroidInjector<Activity> {
        override fun inject(activity: Activity) {
            if (activity.isChatActivity()) { 
              component.chatComponent().activityInjector().inject(activity)
            } else {
              component.activityInjector().inject(activity)
         }
     }
  }
}

component has access to chatComponent() but how do I get chatComponent().activityInjector() and component.activityInjector()?

You can add methods like:

@Component
interface RootComponent {
  fun activityInjector(): DispatchingAndroidInjector<Activity>
  fun ChatComponent chatComponent()
}

@Subcomponent
interface ChatComponent {
  fun activityInjector(): DispatchingAndroidInjector<Activity>
}

Sorry for reviving this but FYI for anyone trying to implement this, ronshapiro's doesn't work if quite work if you subclass DaggerApplication since it actually returns a DispatchingAndroidInjector which is final instead of an AndroidInjector. Instead here's my application class:

class MyApplication : Application(), HasActivityInjector {

    @Inject
    lateinit var appDispatchingActivityInjector: DispatchingAndroidInjector<Activity>

    private val appComponent: AppComponent by lazy {
        DaggerAppComponent
                .builder()
                .application(this)
                .build()
    }

    var userSessionComponent: UserSessionComponent? = null

    override fun onCreate() {
        super.onCreate()
        appComponent.inject(this)
    }

    override fun beginUserSession() {
        userSessionComponent = appComponent.userSessionComponent().build()
    }

    override fun endUserSession() {
        userSessionComponent = null
    }

    override fun activityInjector(): AndroidInjector<Activity> {
        return AndroidInjector {
            // Try to inject using more tightly scoped components first.
            userSessionComponent?.activityInjector()?.maybeInject(it) ?: false ||
                    appDispatchingActivityInjector.maybeInject(it)

        }
    }
}

An advantage of this approach is that you don't have to duplicate your @ContributesAndroidInjector activity bindings in activityInjector()

Disclaimer: Hack

Lets say I have an app component as mentioned below

@Component(
    modules = [
        AndroidSupportInjectionModule::class,
        MainActivityModule::class]
)
interface AppComponent {
    fun inject(application: MyApplication)
}

Then I have a sub component as mentioned below.

@Module
interface MainActivityModule {
    @ContributesAndroidInjector(modules = [SecondActivityModule::class])
    fun contributeMainActivityInjector(): MainActivity
}

Then I have a nested subcomponent as mentioned below.

@Module
interface SecondActivityModule {
    @ContributesAndroidInjector
    fun contributeMainActivityInjector(): SecondActivity
}

Invoking AndroidInjection.inject(this) from SecondActivity will throw an exception because AndroidInjection class try to use DispatchingAndroidInjector<Activity> injected in application class which does not have AndroidInjector.Factory for SecondActivity. But that factory is available in MainActivity DispatchingAndroidInjector<Activity>

So hack is every time an application class implements HasActivityInjector interface, it can store/update DispatchingAndroidInjector<Activity> in application class.

@dalvin your component hierarchy is faulty in the first place. Activities are independent of each other, and having the second activity injector inside the main activity doesn't work. You'll run into issues when android restores your state after a background app death. You should organize your components so that the activity android injectors are all at the application component level.

Was this page helpful?
0 / 5 - 0 ratings