简体   繁体   中英

Dagger 2.10 Android subcomponents and builders

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?

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.

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM