Dagger: AndroidInjection doesn't allow to inject a super class

Created on 23 Mar 2017  路  10Comments  路  Source: google/dagger

I discovered this issue playing with AndroidAnnotations but it should be the same for every scenario where a Fragment extends another Fragment and the base one use AndroindInjection.inject.

The detailed scenario:

MyFragment use AndroidInjection to inject the presenter. Following the Subcomponent, module and Fragment:

@Subcomponent
public interface MyFragmentSubComponent extends AndroidInjector<MyFragment> {

  @Subcomponent.Builder
  abstract class Builder extends AndroidInjector.Builder<MyFragment> {
  }
}
@Module(subcomponents = { MyFragmentSubComponent.class })
abstract class FragmentModule {

  @Binds
  @IntoMap
  @FragmentKey(MyFragment.class)
  abstract AndroidInjector.Factory<? extends Fragment> bindMyFragmentInjectorFactory(
      MyFragmentComponent.Builder builder);
}
@EFragment(R.layout.screen_my_fragment)
public class RMyFragment extends BaseFragment implements RecipeBrowserPageView {
 @Inject
  MyPresenter presenter;

  @Override
  public void onAttach(Context context) {
    AndroidSupportInjection.inject(this);
    super.onAttach(context);
  }
}

``

AndroidAnnotations will generate the class MyFragment_ extending MyFragment.
If I try to use MyFragment_ this error will be raised:
Caused by: java.lang.IllegalArgumentException: No injector factory bound for Class<com.package.MyFragment_>. Injector factories were bound for supertypes of com.package.MyFragment_: [com.package.MyFragment]. Did you mean to bind an injector factory for the subtype?

Because obviously the subcomponent is for the base MyFragment.

I'd like to try adding a class parameter to AndroidInjector.inject, and force to look for the correct injector based on the class parameter, and inject the instance parameter.
It seems possible to you?

Could anyone suggest a different approach or a solution for this problem?

Thanks very much.

Most helpful comment

I struggle with the same problem. I want to write the Dagger Code only once for my BaseActivity, so all other activities inherits from this one and could easily be injected. But as I get the Dagger 2 concept right that means, I have to write a SubComponent, a Module and an injector factory for all my 10 Activities. In what way should that kind of DI makes developers live easier?!

All 10 comments

@LuigiPapino Add your FragmentModule to parent component

@UsherBaby thanks for the advice. The project is setup correctly, with the module added to the parent component. I didn't include in the example for shortness.

In other words, the problem is that AndroidInjector.inject looks for the class name at runtime, so it's not able to inject a super class.

Here the relevant piece of code:

  /**
   * Attempts to perform members-injection on {@code instance}, returning {@code true} if
   * successful, {@code false} otherwise.
   *
   * @throws InvalidInjectorBindingException if the injector factory bound for a class does not
   *     inject instances of that class
   */
  public boolean maybeInject(T instance) {
    Provider<AndroidInjector.Factory<? extends T>> factoryProvider =
        injectorFactories.get(instance.getClass());
    if (factoryProvider == null) {
      return false;
    }

    @SuppressWarnings("unchecked")
    AndroidInjector.Factory<T> factory = (AndroidInjector.Factory<T>) factoryProvider.get();
    try {
      AndroidInjector<T> injector =
          checkNotNull(
              factory.create(instance),
              "%s.create(I) should not return null.",
              factory.getClass().getCanonicalName());

      injector.inject(instance);
      return true;
    } catch (ClassCastException e) {
      throw new InvalidInjectorBindingException(
          String.format(
              "%s does not implement AndroidInjector.Factory<%s>",
              factory.getClass().getCanonicalName(), instance.getClass().getCanonicalName()),
          e);
    }
  }

However I'll prepare a project as reference to explain better the scenario.

Here the project that replicate the scenario: https://github.com/LuigiPapino/DaggerAndroidInjectorSuperClass/tree/master

A BaseActivity with a BasePresenter injected through the AndriondInection.inject;
Then a MainActivity extends the BaseActivity.

Launching the MainActivity will raise this exception:
Caused by: java.lang.IllegalArgumentException: No injector factory bound for Class<net.dragora.daggerandroidinjectorsuperclass.MainActivity>. Injector factories were bound for supertypes of net.dragora.daggerandroidinjectorsuperclass.MainActivity: [net.dragora.daggerandroidinjectorsuperclass.BaseActivity]. Did you mean to bind an injector factory for the subtype?

You need to have the AndroidInjector bound for the actual type that's being injected. Members injection can have weird implications for subtypes that have injected members of their own. In your initial code, switch to AndroidInjector<MyFragment_> and @ActivityKey(MyFragment_)

This has been the first solution came in my mind, and I received a build error that I don't remember now.

I tried again now, and works well. Don't know what mess I did.
Thanks very much!

Hi @LuigiPapino you said that your demo works well. I cloned your repository and had the same issue, do you mind to check if some changes was not pushed to your demo?

Could you upload the solution to your demo app? thanks! :)
@ronshapiro could you be more specific as which line to change, and how to change it (exactly)?

I had a same issue, too. To fix this issue, every subclass has to be added in the module explicitly even if subclass doesn't have any @Inject.

For example, if there are BaseFragment and MainFragment..

@Module
public abstract class FragmentModule {
    @ContributesAndroidInjector
    abstract BaseFragment contributeBaseFragmentInjector();

    @ContributesAndroidInjector
    abstract MainFragment contributeMainFragmentInjector();
}

And if you wanna fix @LuigiPapino's code, change ActivityModule.java:L19, BaseActivity.class -> MainActivity.class

Is it possible to have the members injected for the sub classes too without specifying the sub-class name? This would be really nice, where you could use the injection interfaces among different libraries.

I struggle with the same problem. I want to write the Dagger Code only once for my BaseActivity, so all other activities inherits from this one and could easily be injected. But as I get the Dagger 2 concept right that means, I have to write a SubComponent, a Module and an injector factory for all my 10 Activities. In what way should that kind of DI makes developers live easier?!

Was this page helpful?
0 / 5 - 0 ratings