17

Using the new (in 2.10) dagger.android classes, I'm trying to inject things using a Subcomponent that depends on other Modules, and, therefore, has a Builder with setters for those modules. The documentation on https://google.github.io/dagger/android.html describes this, but is not clear on how to actually write and/or invoke those setters.

Quoting from the above link:

AndroidInjection.inject() gets a DispatchingAndroidInjector from the Application and passes your activity to inject(Activity). The DispatchingAndroidInjector looks up the AndroidInjector.Factory for your activity’s class (which is YourActivitySubcomponent.Builder), creates the AndroidInjector (which is YourActivitySubcomponent), and passes your activity to inject(YourActivity).

It seems to me that in order to be able to call the setters for the Builder, I need to get in there somewhere and ensure the Builder has all the necessary data? The problem I'm seeing is that at runtime, I get an IllegalStateException: MODULE must be set, when the generated builder for my Subcomponent is invoked by AndroidInjector.

The Subcomponent in question is in fact for a Fragment, not an Activity, but I'm not sure that should matter. Any ideas about how to do this?

Petter Måhlén
  • 225
  • 2
  • 9

2 Answers2

30

In short, you're supposed to override the call to seedInstance on the Builder (which is an abstract class instead of an interface) to provide other modules you need.

edit: Before you do, check and make sure that you really need to pass that Module. As Damon added in a separate answer, if you're making a specific Module for your Android class, you can rely on the automatic injection of that class to pull the configuration or instance out of the graph at that point. Favor his approach if it's easier just to eliminate the constructor parameters from your Module, which also may provide better performance as they avoid unnecessary instances and virtual method calls.


First, dagger.android in 30 seconds: Rather than having each Activity or Fragment know about its parent, the Activity (or Fragment) calls AndroidInjection.inject(this), which checks the Application for HasActivityInjector (or parent fragments, activity, and application for HasFragmentInjector). The idea is that you contribute a binding to a multibindings-created Map<Class, AndroidInjector.Factory>, where the contributed bindings are almost always subcomponent builders you write that build object-specific subcomponents.

As you might tell from AndroidInjection.inject(this) and AndroidInjector.Factory.create(T instance), you don't get a lot of opportunity to pass Activity-specific or Fragment-specific details to your Builder. Instead, the idea is that your subcomponent builder overrides the seedInstance implementation. As in the docs for seedInstance:

Provides instance to be used in the binding graph of the built AndroidInjector. By default, this is used as a BindsInstance method, but it may be overridden to provide any modules which need a reference to the activity.

This should be the same instance that will be passed to inject(Object).

That'd look something like this:

@Subcomponent(modules = {OneModule.class, TwoModule.class})
public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {

  // inject(YourActivity) is inherited from AndroidInjector<YourActivity>

  @Builder
  public abstract class Builder extends AndroidInjector.Builder<YourActivity> {
    // Here are your required module builders:
    abstract Builder oneModule(OneModule module);
    abstract Builder twoModule(TwoModule module);

    // By overriding seedInstance, you don't let Dagger provide its
    // normal @BindsInstance implementation, but you can supply the
    // instance to modules or call your own BindsInstance:
    @Override public void seedInstance(YourActivity activity) {
      oneModule(new OneModule(activity));
      twoModule(new TwoModule(activity.getTwoModuleParameter()));
    }
  }
}

The assumption here is that you need to wait for the activity instance for the modules. If not, then you also have the option of calling those when you bind the subcomponent:

@Provides @IntoMap @ActivityKey(YourActivity.class)
AndroidInjector.Factory bindInjector(YourActivitySubcomponent.Builder builder) {
  return builder
      .oneModule(new OneModule(...))
      .twoModule(new TwoModule(...));
}

...but if you can do that, then you could more-easily take care of those bindings by overriding those modules, implementing a zero-arg constructor that can supply the Module's constructor parameters, and letting Dagger create those as it does for any Modules with public zero-arg constructors.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • 4
    This totally worked! It wasn't mentioned anywhere in the docs though which makes the doc difficult to understand for new users using dagger 2.+. – ArunL May 22 '17 at 08:11
  • not sure why my implementation return an error as "Error:(154, 5) error: method does not override or implement a method from a supertype" – Damon Yuan Nov 25 '17 at 14:35
  • based on https://github.com/google/dagger/issues/615, ```class MyActivityModule { @Provides static SomethingDerivedFromMyActivity providesMethod(MyActivity myActivity) { return myActivity.somethingDerived(); } }``` is good enough. – Damon Yuan Nov 25 '17 at 15:01
  • Thanks Damon! Commented on your answer, and edited my answer to include a pointer to yours. – Jeff Bowman Nov 25 '17 at 16:00
  • What do you mean by "are *almost* always subcomponent builders"? Can they be something other than subcomponent (builders)? – arekolek Apr 05 '18 at 11:35
  • @arekolek: If you use `@ContibutesAndroidInjector` you'll get a per-Fragment (or per-Activity or per-Service) `@Subcomponent.Builder` for which a call to `create(T)` calls `@BindsInstance seedInstance(T)` and then `inject(T)` is a members-injection method. However, if you use `@Provides @IntoMap` you can install any conforming implementation you'd like. You may find this useful for making test implementations, or delegating implementations that do something else first, or for future versions of the framework. – Jeff Bowman Apr 05 '18 at 14:35
  • I think those methods that we want to call from `seedInstance` need to be declared `public`, otherwise Dagger complains that the method "is not abstract and does not override abstract method". – arekolek Jun 13 '18 at 07:53
  • @JeffBowman Could you tell me, is android-dagger supposed to replace the pattern shown by Greg Kick in this video (time stamp): https://www.youtube.com/watch?v=iwjXqRlEevg&feature=youtu.be&t=1693&ab_channel=MCEConference – Florian Walther Aug 06 '19 at 10:48
  • @FlorianWalther He is describing dagger.android's implementation as of 2016, yes. (Though HasActivitySubcomponentBuilders became HasActivityInjector, which turned into the all-purpose interface HasAndroidInjector.) – Jeff Bowman Aug 06 '19 at 11:29
3

that does work, but it's unnecessary. the seedInstance method provides the activity instance into the graph, so you can have MyActivityModule with no state, and just request MyActivity in your @Provides methods.

class MyActivityModule {
  @Provides
  static SomethingDerivedFromMyActivity providesMethod(MyActivity myActivity) {
    return myActivity.somethingDerived();
  }
}

Doing this saves the module instance and allows the generated factories to be leaner.

from https://github.com/google/dagger/issues/615.

Damon Yuan
  • 3,683
  • 4
  • 23
  • 31
  • +1: You (and my colleague Ron) are right, of course, assuming that your setup is refactorable enough to edit the module to include a reference to your Activity. If your Module is from a third party, or is designed to work for more Activities than just your MyActivity, then you may have to go with my more-complicated setup (which is itself also suggested in the docs). I answered this question assuming the asker needs stateful modules with constructors, because they mentioned needing a Builder, but if you don't then of course things can be represented more easily. – Jeff Bowman Nov 25 '17 at 15:53
  • 1
    Agree but my problem is that when using your solution, the compiler protests loudly with "Error:(154, 5) error: method does not override or implement a method from a supertype". I have completely follow the steps and after check the generated code, I find the error was given on the implementation of `oneModule()` abstract method. Not sure why it happens and I am using dagger 2.10. Anyway, let me do some more test, maybe I can find somewhere wrong in my code. – Damon Yuan Nov 28 '17 at 02:00
  • Can you show me? It sounds like `oneModule` wasn't public or abstract, or the class wasn't, or somesuch. – Jeff Bowman Nov 28 '17 at 06:15