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 Thing
s on demand, and have each of those Thing
s have their own subcomponent instance associated with them?
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
.