Simas
Simas

Reputation: 44118

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

Upvotes: 2

Views: 344

Answers (1)

Jeff Bowman
Jeff Bowman

Reputation: 95634

@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.

Upvotes: 1

Related Questions