4

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 ?

BugsBunnyBR
  • 1,004
  • 9
  • 19

1 Answers1

5

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

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • Jeff, thanks for your reply. I could not understand some things.. like activityInjector returns a DispatchingAndroidInjector and not an AndroidInject object... so I am kinda lost here. – BugsBunnyBR Mar 30 '17 at 03:52
  • Ah, found it: Since Dagger 2.10, in the master branch, [HasDispatchingActivityInjector](https://github.com/google/dagger/blob/dagger-2.10-rc1/java/dagger/android/HasDispatchingActivityInjector.java) became [HasActivityInjector](https://github.com/google/dagger/blob/master/java/dagger/android/HasActivityInjector.java) and --which is important for my answer, because that's what lets you write your own implementation. – Jeff Bowman Mar 30 '17 at 06:34
  • So... my Application should not implement the HasDispatchingActivityInjector? Just the HasActivityInjector? – BugsBunnyBR Mar 30 '17 at 15:07
  • If you update to head, HasDispatchingActivityInjector will not exist. – Jeff Bowman Mar 30 '17 at 15:14
  • @Jeff Bowman - Can you please explain how to get the HEAD builds? Whenever you have a minute? – Jon Merritt Mar 30 '17 at 18:48
  • 1
    @JonMerritt I just mean what's available on [the "master" branch on the Dagger 2 project on Github](https://github.com/google/dagger). You can download those files as a zip, or clone onto your machine. The JAR isn't in Maven yet, but I'd bet it'll be in 2.11. – Jeff Bowman Mar 30 '17 at 18:57
  • Oh ok. I figured as much. Just wanted to make sure. Thanks for getting back so quick. – Jon Merritt Mar 30 '17 at 19:00
  • It seems the provided solution is not applicable to the stable version of dagger... so it wouldn`t be a good fit to me. I created a github repo to try show the problem and maybe receive some feedback. https://github.com/BugsBunnyBR/AndroidInjector My idea is that the dagger module AuthModule should provide (share the instance) AuthRepository implementation (in AuthRepositoryModule) to Login/ForgetPassword/Registration activities. The current version of the project seems to compile, but fails when accessing the auth package activities. Thanks for all the help. – BugsBunnyBR Mar 31 '17 at 22:22
  • @Jeff Bowman Sorry to bug you and this thread but just making sure I have things setup correctly, made a working simple app here - https://github.com/JonathanMerritt/BoneDagger . Could you possibly glance it over and let me know if you ever have the time? It's spliced together from another project but it's should be clear – Jon Merritt Apr 01 '17 at 22:47