How to create custom scoped modules in dagger 2.10

2019-02-18 11:50发布

问题:

I am trying to migrate the company app to dagger 2.10 and the AndroidInjector.inject method, but I think I found a problem. The app uses custom scopes… like the feature Login has 3 activities (each one with it`s own dagger module) and one LoginModule that is responsible for sharing singletons that only should live in this scope. So in the first Activity I used to execute something like:

public class LoginActivity extends AppCompatActivity{
public void onCreate(Bundle bla){
LoginActivityComponent activityComponent = ((CustomApplication) getApplicationContext())
                .plus(new LoginModule()) // generates LoginComponent and save the reference in the CustomApplication
                .plus(new LoginActivityModule(this));
        activityComponent.inject(this);
      ...
}

In the others activities I just execute ((CustomApplication) getApplicationContext()).getLoginComponent().plus(new ForgetPasswordModule()).inject(this)

How can I archive the same behavior when using AndroidInjector ?

回答1:

The single-subcomponent cheat

Rather than the normal implementation in your Application:

public class YourApplication extends Application implements HasActivityInjector {
  @Inject DispatchingAndroidInjector<Activity> dispatchingActivityInjector;

  @Override
  public AndroidInjector<Activity> activityInjector() {
    return dispatchingActivityInjector;  // Always get it from Multibindings.
  }
}

Just move the activity-binding modules to your LoginComponent and delegate to the DispatchingAndroidInjector<Activity> from your LoginComponent instead:

@Override
public AndroidInjector<Activity> activityInjector() {
  return getOrCreateLoginComponent().getActivityInjector();
}

This is the least amount of ongoing maintenance, but it seems pretty backwards, because you're creating your LoginComponent up front. However, if LoginComponent is cheap and is your only subcomponent of this style, then everything works perfectly: LoginComponent's injector can see the multibindings in its parents, so LoginComponent's ActivityInjector will always work even for bindings in the parent.

Because the bindings of non-login activities still reside in the ApplicationComponent, those activities won't be able to use bindings from the parent component. Otherwise, though, this is tantamount to merging your LoginComponent into your ApplicationComponent, which probably isn't an option or else you'd've done it that way.

Delegating AndroidInjector

If your LoginComponent is expensive to create, then as an alternative you could move the getOrCreateLoginComponent() call behind an instanceof check:

@Inject DispatchingAndroidInjector<Activity> dispatchingActivityInjector;

@Override
public AndroidInjector<Activity> activityInjector() {
  return new AndroidInjector<Activity>() {
    @Override public void inject(Activity activity) {
      if (Activity instanceof LoginActivity
          || Activity instanceof OtherLoginActivity) {
        getOrCreateLoginComponent().getActivityInjector().inject(activity);
      } else {
        // You can chain other subcomponents here as well.
        dispatchingActivityInjector.inject(activity);
      }
    }
  };
}

This means that you would need to keep a separate list (maybe as a field in LoginComponent or LoginModule) of activities that LoginComponent can handle, but if you want to avoid instantiating LoginComponent until you're sure that you're injecting a login-related activity, here's how you'd check it. The above approach also scales well to multiple subcomponents, because you're always calling inject on exactly one DispatchingAndroidInjector<Activity> from exactly one Component.

Hybrid alternative

Because the Map presence check is likely pretty fast, you could also avoid that extra list by checking with the main injector first before throwing it to the login component. Of course, that starts to get ugly if you've got multiple subcomponents of that style.

@Inject DispatchingAndroidInjector<Activity> dispatchingActivityInjector;

@Override
public AndroidInjector<Activity> activityInjector() {
  return new AndroidInjector<Activity>() {
    @Override public void inject(Activity activity) {
      if (!dispatchingActivityInjector.maybeInject(activity)) {
        // It's not in the top level. Start checking subcomponents.
        getOrCreateLoginComponent().getActivityInjector().inject(activity);
      }
    }
  };
}

Hopefully, between the three, you won't find Android injection here to be "worse than pain from any disease or wound known in the universe".