Search code examples
dependency-injectiondagger-2dagger

Dagger: Is it possible to automatically expose Subcomponent getters to a sibling Subcomponent's scope?


I have a root component and two sibling subcomponents (let's call them Root, A, B). The subcomponents generally need to stay isolated from one another.

However, A does expose some APIs that B needs access to. Today, we do this like so:

Root rootComponent = buildRootComponent();

A aComponent = rootComponent.getAComponentBuilder().build();

B bComponent = rootComponent.getBComponentBuilder()
  .bindX(aComponent.getX())
  .bindY(aComponent.getY())
  .bindZ(aComponent.getZ())
  // etc...
  .build();

This gives B access to specific APIs from A, without giving access to everything. This is great.

However, it is also a lot of tedious overhead. Whenever we want to pass a new thing from A to B, we need to add code in three places:

  1. Write a getter in B:
@B
Q getQ();
  1. Write a binder in A's Builder:
@BindsInstance
Builder setQ(Q q);
  1. Write the glue code (as mentioned above) that ties the two together:
B bComponent = rootComponent.getBComponentBuilder()
    //...
    .setQ(bComponent.getQ())
   .build();

I want to skip steps 2 & 3. I want all defined getters on B to be automatically bound inside of A, without exposing the rest of A to B. Is this possible?


Solution

  • Yes, by specifying a component dependency, you can effectively have every "provision method" (zero-arg getter) on the target component added as a delegating provider as you manually wrote it here. You'll have BComponent list AComponent as a dependency, which also means you'll need to provide an instance of AComponent in the Component.Factory or Component.Builder interface you define for BComponent. (For that matter, as in the docs, the "component" you depend on can be any class or interface regardless of whether it has Dagger annotations or has an implementation generated by Dagger.)

    Be careful with this layout: For one, expressing a mix of AComponent and BComponent may be difficult for future readers to understand, especially since AComponent and BComponent share a hierarchy and may behave counterintuitively depending on where and how you define common providers. Furthermore, if you use multiple small build targets you might find it difficult or expensive for BComponent.java to import AComponent directly because AComponent will depend on all of AComponent's interfaces and impls via its concrete list of Modules. (You can work around this if you have BComponent depend on an AComponentInterface you create without annotations, put your getters on that interface, and have AComponent extend AComponentInterface to avoid repetition; this all exacerbates the risk of making your build and object graphs too complicated to understand.)