5

Hi community I have problem with understanding dagger 2 adding subcomponent in the new way (added in dagger 2.7). See example below:

@Component(modules = {AppModule.class, MainActivityBinder.class})
@Singleton
interface AppComponent
{
   inject(MyApplication _)
}

@Subcomponent(modules = ActivityModule.class)
interface ActivitySubcomponent
{
   inject(MainActivity _)

   @Subcomponent.Builder
   interface Builder
   {
      @BindInstance
      Builder activity(Activity activity)

      ActivitySubcomponent build();
   }
}

Initial step: I have AppComponent with is my root component, that provide AppModule with singletons (retrofit, okhttp etc.) In ActivitySubcomponent I provide ActivityModule with has dependencies specified to that activity. Now subcomponent must be added to AppComponent, so in new way I create specified module called MainActivityBinder, that has annotations @Module.subcomponents with point to that binds subcomponent, but I have first problem, what should be in body of that bind module ?

@Module(subcomponents = ActivitySubcomponent.class)
public class MainActivityBinder
{
  //what body of this class should be ??
}

I know, that idea is that I can bind subcomponent or their builder. Second question when bind builder, and when bind subcomponent ? For example my ActivitySubcomponent required activity context, so I create builder that provide context to ActivityModule in this case will be better to provide in MainActivityBinder a builder ? Third question how invoke component builder and how obtain subcomponent for app component ? In standard subcomponent factory I added to AppComponent method that return subcomponent and I can define params (for example give activity context, listed below)

@Component(modules = {AppModule.class})
@Singleton
interface AppComponent
{
   ActivitySubcomponents newActivitySubcomponents(Activity activity);

   inject(MyApplication _);
}

// in MainActivity
appComponent.newActivitySubcomponents(this).build().inject(this);

so have achieve this behavior in new subcomponent added method ?

Heroes84
  • 844
  • 1
  • 8
  • 17
  • 1
    Why don't use dagger-android ? There are everything much more simpler. – Zuluft Jun 29 '18 at 12:25
  • 3
    @Zuluft You suggestion is off topic, my goal is to *understand* Module.subcomponents, i read documentation that saids: "Using Module.subcomponents is better since it allows Dagger to detect if the subcomponent is ever requested. Installing a subcomponent via a method on the parent component is an explicit request for that component, even if that method is never called. ", so I want try to test how work new way, but i cannot properly defined binding module. – Heroes84 Jun 29 '18 at 12:43

1 Answers1

1
  1. Your module MainActivityBinder is allowed to be empty, and should be if you don't have anything else to bind with it. Empty (annotation-only) modules are also useful when you only use Module.includes, such as when you want to keep a module list in one place rather than duplicating it among several components. The subcomponents attribute on the annotation is enough for Dagger to understand what you're trying to do.

  2. You can inject a FooSubcomponent or Provider if and only if it has no @BindsInstance methods or instantiable modules (that Dagger can't instantiate). If all of your modules are interfaces, abstract classes, or modules that have public zero-arg constructors, then you may inject the subcomponent directly. Otherwise you should inject your subcomponent builder instead.

  3. You can get to your subcomponent builder by creating a method that returns it on your AppComponent, just as you can for any binding that exists in the graph:

    @Component(modules = {AppModule.class, MainActivityBinder.class})
    @Singleton
    interface AppComponent {
      ActivitySubcomponent.Builder activitySubcomponentBuilder();
    
      inject(MyApplication _)
    }
    

    You may also inject it into the object of your choice.

    @Inject ActivitySubcomponent.Builder activitySubComponentBuilder;
    activitySubComponentBuilder.activity(this).build().inject(this);
    
    // You can also inject a Provider<ActivitySubcomponent.Builder> if you want,
    // which is a good idea if you are injecting this directly into your Application.
    // Your Application will outlive your Activity, and may need to inject several
    // instances of the Activity across application lifetime.
    @Inject Provider<ActivitySubcomponent.Builder> activitySubComponentBuilderProvider;
    activitySubComponentBuilderProvider.get().activity(this).build().inject(this);
    

Though there doesn't appear to be much advantage to injecting the subcomponent builder when you could just as easily call the method on the component (either to return the Builder or return the subcomponent), there are a couple of advantages to injecting the builder:

  • Dagger can't tell whether you call a method on the component, so it generates and compiles the code even if your subcomponent is not consumed. Dagger can tell whether you ever try to inject the builder, so if there are no subcomponent/builder injections and no methods, Dagger will skip generating code for the subcomponent.
  • If your codebase is large enough that you have to split it up into different targets to compile, the factory method technique might catch you in some dependency cycles, where your app's component and modules depend on everything and you can only get to your subcomponent from your component itself. With injectable subcomponent builders, you have more options about how to get to your subcomponent or builder.
Jeff Bowman
  • 74,544
  • 12
  • 183
  • 213
  • Thanks @Jeff Bowman for deep explain. I have one more question about swapping subcomponent for testing, according to second option from official [documentation](https://google.github.io/dagger/testing.html). How can I configure `AppComponent` for set test subcomponent `MainActivitySubcomponentTest` (that extends from MainActivitySubcomponent), I try do setter in Component.Builder, but not work. – Heroes84 Jul 01 '18 at 11:42
  • @Heroes84: You're welcome! Unfortunately there isn't really a good way to substitute subcomponents without creating a different AppComponentForTest, because the subcomponent implementation is a private detail of the generated component code. If you want to be able to substitute your parent component, then you could make your activity subcomponent into a full `@Component` with dependencies—but that has a lot of separate work and trade-offs. If you structure your injection well (inject subcomponent directly) you could also hand-write or mock a subcomponent and its builder, and use them in tests. – Jeff Bowman Jul 01 '18 at 16:56
  • If I use the AppComponent to inject the builder into the activity member variables, I can't do the same for other fields with the ActivityComponent afterwards, right? In other words, I can't use field injection on the same class with 2 different components. – Florian Walther Aug 02 '19 at 11:01
  • Also, is the unnecessarily generated code even a problem, considering that Proguard is supposed to remove the unused parts? – Florian Walther Aug 02 '19 at 11:15
  • @FlorianWalther 1. Injecting the same object twice (including with two different components) is accident-prone at best, if it works at all; Dagger components don't like injecting classes that they can't inject fully. That said, subcomponents get access to all of their parent components' bindings, so injecting with the Activity component once should do it. 2. Proguard _might_ trim the unused code for you, but you still need to _satisfy all the bindings your unused methods need_, plus your Proguard time increases as your input does and Proguard _might_ not be as effective in removing that code. – Jeff Bowman Aug 02 '19 at 14:34
  • @JeffBowman In your last code snippet above, you have a field `@Inject ActivitySubcomponent.Builder` and then you seem to use this builder to create a component and call `inject(this)`. That's what I am referring to. Is this construct possible? Because in order to get this builder injected, you have to call `inject(this)` from the `AppComponent` as well, right? – Florian Walther Aug 02 '19 at 14:36
  • @FlorianWalther I'll cop to that. It's a bad idea, and hard to get right, but I was trying to stay parallel to the original question's code for consistency's sake. In any case it's unlikely that the Application will have access to the Activity to inject, since Android can create/destroy/change the Activity instance pretty freely, so the real right pattern is to use `dagger.android`. Internally, `dagger.android` creates an activity subcomponent for you, injects a builder map in the application, gets the builder via `getApplication`, builds it, and calls it to `inject(this)` in `onCreate`. – Jeff Bowman Aug 02 '19 at 14:50
  • (I'll add the above to my answer once I'm at a computer, not on my phone.) – Jeff Bowman Aug 02 '19 at 14:51
  • @JeffBowman Thanks for the clarification. I'm trying to find an example where the `Module.subcomponents` approach is used on Android (without `dagger.android`) but it seems that they don't really fit together because we can't constructor inject activities. – Florian Walther Aug 02 '19 at 14:57