Android Dagger 2 with BaseActivity to reduce boile

2019-07-13 15:12发布

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.

1条回答
\"骚年 ilove
2楼-- · 2019-07-13 15:58

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.

@Component public interface YourComponent {
  void injectBase(BaseActivity baseActivity);
  void injectA(ActivityA activityA);
  void injectB(ActivityB activityB);
  void injectC(ActivityC activityC);
}

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 call injectBase, 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 call injectBase, 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 call injectBase and not any specific subtypes. Importantly, this remains true even if all the names injectBase, injectA, and so forth, are all the same (like inject). The JVM will pick the narrowest overload it can determine at compile time, which will be inject(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.

// in BaseActivity
protected abstract void injectDependencies();

// in ActivityA
@Override protected void injectDependencies() { component.injectA(this); }

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

查看更多
登录 后发表回答