Search code examples
androiddagger-2dagger

Fragment injection without a dedicated subcomponent


I'm trying to use android-dagger to inject a fragment from a manually defined subcomponent:

@Component(modules = [
    AndroidSupportInjectionModule::class,
    AppModule::class,
    BuilderModule::class
])
interface AppComponent : AndroidInjector<App> {

    @Component.Builder
    abstract class Builder : AndroidInjector.Builder<App>()

    fun someComponent(): SomeComponent

}

@Subcomponent
interface SomeComponent {

    fun inject(fragment: SomeFragment)

}

Execution fails with:

IllegalArgumentException: No injector factory bound for Class "SomeFragment"


However, if I create a fragment bind annotated with @ContributesAndroidInjector it executes fine. The doc states that all this does is create a subcomponent. Why can't I do that manually?


Minimal working project can be found on github: https://github.com/absimas/fragment-injection


Solution

  • @ContributesAndroidInjector creates a subcomponent, yes. The docs don't say anything more, but they don't assert that this only creates a subcomponent; it also installs it into the Map<Class, AndroidInjector.Factory> multibinding that powers each of dagger.android's injection types.

    You can see an example of this map binding on the Android page of the Dagger User's Guide:

    @Binds
    @IntoMap
    @FragmentKey(YourFragment.class)
    abstract AndroidInjector.Factory<? extends Fragment>
        bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Builder builder);
    

    Note that this expects you to bind a Builder, not a Component: dagger.android expects that you'll want access to your Fragment instance from within your subcomponent, so the binding is for AndroidInjector.Factory<? extends Fragment> such that DispatchingAndroidInjector can call create(Fragment) on it. This is designed to be compatible with Subcomponent.Builder, but it does require that you define your @Subcomponent.Builder nested interface that extends the adapter AndroidInjector.Builder. Pardon my Java on a Kotlin question:

    /** No need for an explicit inject, because you must extend AndroidInjector. */
    @Subcomponent
    interface SomeComponent extends AndroidInjector<SomeFragment> {
    
      /**
       * This abstract class handles the final create(Fragment) method,
       * plus @BindsInstance.
       */
      @Subcomponent.Builder
      abstract class Builder extends AndroidInjector.Builder<SomeFragment> {}
    }
    

    EDIT: It occurs to me now that your title states you don't want a dedicated subcomponent; beyond using a manual definition instead of @ContributesAndroidInjector, if you want a multi-Fragment subcomponent, then you might run into some trouble with this advice: dagger.android requires implementations of AndroidInjector<YourFragment>, and because of erasure, you own't be able to have a single class implement multiple AndroidInjector<T> or AndroidInjector.Builder<T> interfaces or abstract classes. In those cases you might need to write a manual AndroidInjector.Factory implementation which calls the correct concrete inject method. However, this seems like a lot of work for the sake of creating a monolithic Fragment component, when best-practices dictate dagger.android's default: a small and specific component for each Fragment.