Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AndroidInjection doesn't allow to inject a super class #653

Closed
LuigiPapino opened this issue Mar 23, 2017 · 10 comments
Closed

AndroidInjection doesn't allow to inject a super class #653

LuigiPapino opened this issue Mar 23, 2017 · 10 comments

Comments

@LuigiPapino
Copy link

LuigiPapino commented Mar 23, 2017

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.

@tata8k
Copy link

tata8k commented Mar 24, 2017

@LuigiPapino Add your FragmentModule to parent component

@LuigiPapino
Copy link
Author

LuigiPapino commented Mar 24, 2017

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

@LuigiPapino
Copy link
Author

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?

@ronshapiro
Copy link

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_)

@LuigiPapino
Copy link
Author

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!

@skhaz
Copy link

skhaz commented May 15, 2017

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?

@ohadnav
Copy link

ohadnav commented Jul 16, 2017

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)?

@uOOOO
Copy link

uOOOO commented Oct 23, 2017

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

@azatmar
Copy link

azatmar commented Dec 6, 2017

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.

@1amGr00t
Copy link

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?!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants