Search code examples
dependency-injectiondagger-2dagger

Dagger: Accessing item created two levels deep in a subcomponent


I have a subcomponent that I need to pull something out of:

@Subcomponent(modules = {SubModule.class})
@SubScope
public interface SubComp {
  // ...
  Thing getThing();
}

Each time #getThing is called, I need a new instance of Thing.

Thing also has its own orbit of items that need to be created with it. Naturally, my instinct is to create another subcomponent for it:

@Subcomponent(modules = {ModuleThing.class})
@ThingScope
public interface SubCompThing {
  // ...
}

But here is my dilemma: Which piece of code should actually be creating Thing?

If I put a provider into SubModule, I then need to bind that instance into SubCompThing, but I get a warning about binding multiple instances. The warning is fatal in my build, and actually states that the warning will become an error in the future:

@Module(subcomponents = {SubCompThing.class})
interface SubModule {
   @Provides
   static providesThing(SubCompThing.Factory thingCompFactory) {
     Thing thing = new Thing();
     thingComp = thingCompFactory.create(thing);  // Warning about duplicate binding.
     // Do some stuff with thingComp
     return thing;
   }
}

If I have SubCompThing create Thing directly itself, my warning turns into an error with the same problem:

@Module
interface ModuleThing {
    @Provides
    @ThingScope
    static Thing providesThing() {
      return new Thing();
    }
}

@Module(subcomponents = {SubCompThing.class})
interface SubModule {
   @Provides
   static Thing providesThing(SubCompThing.Factory thingCompFactory) {
     thingComp = thingCompFactory.create();
     // Do some stuff with thingComp
     return thingComp.getThing();
   }
}

(Dagger compiles that Thing is bound twice, since there are two providers for it.)

How can I have my top level SubComp return new Things on demand, and have each of those Things have their own subcomponent instance associated with them?


Solution

  • You're going to need to use a qualifier annotation.

    The root of the problem here is that subcomponents inherit bindings from their parent components, so within your deepest subcomponent SubCompThing your exposed Thing binding on your component might very well be injecting the Thing binding that you're installing in SubComp's SubModule...because Dagger doesn't know that your @Provides method in SubModule is itself about to call SubCompThing's getThing() method!

    As we discussed in the comments, what you're describing is very much like Dagger's official documentation on subcomponents for encapsulation, which sneakily depicts but does not describe its reliance on a qualifier annotation:

    @Subcomponent(modules = DatabaseImplModule.class)
    interface DatabaseComponent {
      @PrivateToDatabase Database database();
      /* ^^^^^^^^^^^^ */
    }
    

    That's the disambiguating trick: Your ModuleThing should bind @Provides static @PrivateThing Thing, and your SubCompThing should expose @PrivateThing Thing getThing() such that the only bound unqualified Thing is bound in your SubModule. (Incidentally, this would allow you to inject a Thing in SubCompThing, delegating to SubModule's implementation to create a brand new SubCompThing instance from within ModuleThing's call stack.)

    I've defined a cheap qualifier annotation here, but you're welcome to use @Named to prototype it.

    @Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface PrivateThing {}
    
    @Module
    interface ModuleThing {
      @Provides
      @ThingScope
      @PrivateThing
      static Thing providesThing() {
        return new Thing();
      }
    }
    
    @Subcomponent(modules = {ModuleThing.class})
    @ThingScope
    public interface SubCompThing {
      @PrivateThing getThing();
    }
    

    At that point, your SubModule will work with no additional modifications, abstracting away the creation of Thing to a black-box implementation of SubCompThing (which I prefer slightly, such that you don't have to bring Thing's instantiation details into SubModule). It also means you can keep Thing scopeless so you can instantiate more than one of them: When you call the provider, you'll see something new. That way you can make both thing1 and thing2.