I have a situation where I have a component: CarComponent with scope @CarScope and a subcomponent DriverSubcomponent with scope @DriverScope. Basically car requires a driver and driver requires a helmet.
Here is the car component:
@CarScope
@Component
interface CarComponent {
val driverComponentBuilder: DriverComponent.Builder
fun getCar(): Car
@Component.Builder
interface Builder{
fun build(): CarComponent
@BindsInstance
fun carName(@Named("CNAME") name: String): Builder
@BindsInstance
fun driverName(@Named("DNAME") driverName: String): Builder
}
}
And a driver subcomponent:
@DriverScope
@Subcomponent(modules=[HelmetModule::class])
interface DriverComponent {
fun getDriver(): Driver
@Subcomponent.Builder
interface Builder {
fun build(): DriverComponent
}
}
Helmet module:
@Module
interface HelmetModule {
@Binds
fun bindHelmet(whiteHelmet: WhiteHelmet): Helmet
}
And corresponding classes:
@CarScope
class Car @Inject constructor(@Named("CNAME") private val name: String, private val driver: Driver) {
override fun toString(): String {
return "Car: $name, Driver: $driver, hash: ${super.toString()}"
}
}
class Driver @Inject constructor (@Named("DNAME") private val driverName: String, private val helmet: Helmet){
override fun toString(): String{
println("Driver Name: $driverName")
println("Helmet info: $helmet")
println (super.toString())
return super.toString()
}
}
interface Helmet {
fun putOn(): Boolean
fun takeOff(): Boolean
}
class WhiteHelmet @Inject constructor() : Helmet {
override fun putOn(): Boolean {
println("White Helmet is on")
return true
}
override fun takeOff(): Boolean {
println("White Helmet is off")
return false
}
override fun toString(): String {
return "White Helmet"
}
}
I have noticed that this code will not compile unless I add (modules = [HelmetModule::class])
to CarComponent. It seems that when I call getCar(), it does not use Driver provided by the DriverComponent, but creates all required objects instead,
My goal would be to use Driver provided by the DriverComponent.
What are the ways to achieve this? Is the current behaviour related to the custom scopes I used?
Thanks. Leszek
DriverComponent is a subcomponent of CarComponent. CarComponent is a top-level component. This means:
All of the above is true regardless of your scopes; you could remove them and it would still all be true. However, since you are using scopes:
If the above sounds correct to you—that a DriverComponent can only be created from within a CarComponent instance, after that CarComponent is created—then your use case matches the Dagger Subcomponents for encapsulation docs. All you need to do is bind a single @CarScope
instance of DriverComponent, which you can create using the injectable DriverComponent.Builder. You can then confidently move HelmetModule
and driverName
onto your DriverComponent and its Builder respectively.
@Provides @CarScope DriverComponent provideDriverComponent(DriverComponent.Builder builder) {
return builder.build()
}
Or in Kotlin:
@Provides @CarScope
fun provideDriverComponent(builder: DriverComponent.Builder) = builder.build()
Your Car won't be able to directly get to your Driver or their Helmet, but you can get to those by adding methods to your DriverComponent and getting them from that instance. You could even write a Provider that returns a Helmet in CarComponent by injecting your DriverComponent and returning driverComponent.helmet
.
If that doesn't look right—maybe your maybe your DriverComponent doesn't need any CarComponent bindings, and your Driver should be able to be created without a CarComponent instance—then you might need to avoid the Subcomponent and have your Driver or DriverComponent passed into your CarComponent.Builder.