I'm having some troubles while I want to move some Dagger 2 boilerplate code in each activity to a BaseActivity.
BaseActivity extends AppCompatActivity
I have multiples activities, like:
ActivityA extends BaseActivity implements InterfaceA;
ActivityB extends BaseActivity implements InterfaceB;
...
In each activity I have a methods like this (where X is A, B, C, ... for each activity):
public void initActivity() {
ComponentX compX;
...
compX = appComponent.plus(new ModuleX(this)); // this == InterfaceX
...
compX.inject(this); // this == ActivityX
}
I was trying to reduce this code, moving it to the parent BaseActivity. But I'm having some problems to do it. I think that maybe with generics I could do it, but I don't know exactly how.
Here's the question: Which
inject
method are you calling, and can Java determine that at compile time?As described in "A note about covariance", Dagger will generate code for any members-injection method you define, but only the static type you pass in.
When calling
injectA
and passing an ActivityA instance, you'll get injection for the fields defined in ActivityA including the fields in BaseActivity. Same with ActivityB and ActivityC. However, if you callinjectBase
, Dagger will only inject the fields belonging to BaseActivity, even if the object you pass in happens to be an ActivityA, ActivityB, or ActivityC. Dagger generates code at compile time, so if you callinjectBase
, the injection will only happen for the fields on BaseActivity—because that's the code that was generated for BaseActivity's members injector, and those are the only fields Dagger knows how to inject for a BaseActivity parameter.Naturally, because BaseActivity only knows that
this
is a subtype of BaseActivity, it can only callinjectBase
and not any specific subtypes. Importantly, this remains true even if all the namesinjectBase
,injectA
, and so forth, are all the same (likeinject
). The JVM will pick the narrowest overload it can determine at compile time, which will beinject(BaseActivity)
, which will inject BaseActivity's fields and nothing in subtypes. If you were to name them uniquely, you'd see which one you're calling, and why it's not injecting subtype fields.Generics won't help here: You're looking for your Component to generate and call members injectors for ActivityA, ActivityB, and ActivityC. Generics will be erased, but furthermore the component can't take an arbitrary subclass of BaseActivity: Dagger can't generate code at compile time for types it might only encounter at runtime. You really need to prepare those types in Dagger at compile time.
One way around this is to allow the subtypes to inject themselves. The subtypes know that
this
is ActivityA (and so forth), and even though the code might look character-for-character the same, Java can identify the right type and compile it correctly.However, there's another recently-released option, using dagger.android, which uses Multibindings and (effectively) a
Map<Class, MembersInjector>
to dynamically inject the specific type you want. This works from a superclass, too, to the point that you can have your Activity extend DaggerActivity and everything will work just the way you'd like. (Consult the dagger.android.support package for your AppCompatActivity equivalent DaggerAppCompatActivity.)