Apolunor Jing
Apolunor Jing

Reputation: 11

How to use AndroidInjection in base class?

I have a BaseUiFragment in base module, need inject a UiComponent.

public abstract class BaseUiFragment extends Fragment {
    @Inject
    UiComponent mUiComponent;

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

@Subcomponent
public interface BaseUiFragmentSubcomponent extends AndroidInjector<BaseUiFragment> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<BaseUiFragment> {
    }
}

@Module(subcomponents = BaseUiFragmentSubcomponent.class)
public abstract class BaseUiFragmentModule {
    @Binds
    @IntoMap
    @FragmentKey(BaseUiFragment.class) // key in MapProviderFactory
    abstract AndroidInjector.Factory<? extends Fragment>
    bindBaseUiFragmentInjectorFactory(BaseUiFragmentSubcomponent.Builder builder);

    private BaseUiFragmentModule() {}
}

In app module, UiComponentModule provide UiComponent, MainFragment extends BaseUiFragment.

@Module
public class UiComponentModule {
    @Provides
    static UiComponent provideUiComponent() {
        return new UiComponent() {};
    }
}

@Singleton
@Component(modules = {AndroidSupportInjectionModule.class, BaseUiFragmentModule.class, UiComponentModule.class})
public interface ApplicationComponent extends AndroidInjector<MainApplication> {
    @Component.Builder
    abstract class Builder extends AndroidInjector.Builder<MainApplication> {
    }
}

public class MainFragment extends BaseUiFragment {
    @Override
    public View onCreateViewImpl(Bundle savedInstanceState) {
        return new View(getContext());
    }
}

when AndroidSupportInjection.inject(this); run, it does not work. Because DispatchingAndroidInjector's maybeInject() return false

injectorFactories has (BaseUiFragment.class, ...) not has (MainFragment.class, ...), but AndroidSupportInjection.inject(this); this is MainFragment.

public boolean maybeInject(T instance) {
    Provider<AndroidInjector.Factory<? extends T>> factoryProvider =
        injectorFactories.get(instance.getClass());
    if (factoryProvider == null) { // factoryProvider is null
      return false;
    }
    // ...
}

So, How to use AndroidInjection(AndroidSupportInjection) in base class?

After a few days of analysis:

Google's inject impl: it's only instance.getClass()

public boolean maybeInject(T instance) {
    Provider<AndroidInjector.Factory<? extends T>> factoryProvider =
            injectorFactories.get(instance.getClass());
    if (factoryProvider == null) {
        return false;
    }
    // ...
}

My impl: traversal it and its superclass,the problem is solved, but it use reflection that get the factoryProvider.

public boolean maybeInject(T instance) {
    Provider<AndroidInjector.Factory<? extends Fragment>> factoryProvider
            = injectorFactories.get(fragment.getClass());
    Class fragmentSuperclass = fragment.getClass().getSuperclass();
    while (factoryProvider == null && fragmentSuperclass != Fragment.class) {
        factoryProvider = injectorFactories.get(fragmentSuperclass);
        fragmentSuperclass = fragmentSuperclass.getSuperclass();
    }
    if (factoryProvider == null) {
        return false;
    }
    // ...
}

So, is it only this way? And Google can Change the implementation?

Upvotes: 1

Views: 905

Answers (1)

TrevJonez
TrevJonez

Reputation: 959

You have only created a subcomponent that knows how to inject BaseUiFragment. Since that is all that dagger can see it will only know how to generate code to handle injecting the BaseUiFragment.

You need to create a subcomponent for each leaf of your inheritance hierarchy.

Something like this is how i like to do my fragment components

@Subcomponent
public interface MainFragmentComponent extends AndroidInjector<MainFragment> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<MainFragment> {}

    @Module(subcomponents = MainFragmentComponent.class)
    abstract class BindingModule {
        @Binds
        @IntoMap
        @FragmentKey(MainFragment.class)
        abstract Factory<? extends Fragment> mainFragmentComponentBuilder(Builder impl);
    }
}

Upvotes: 2

Related Questions