Lets say I have two interfaces and two classes that implements them.
interface ITest1 {
fun doSomething()
}
interface ITest2 {
fun doSomethingElse()
}
class Test1 @Inject constructor(): ITest1 {
override fun doSomething() {
println("Something")
}
}
class Test2 @Inject constructor(): ITest2 {
@Inject
lateinit var test1: ITest1
override fun doSomethingElse() {
test1.doSomething()
println("Something else ")
}
}
I can make this work if I use @Bind:
@Module
@InstallIn(SingletonComponent::class)
internal abstract class DependenciesBindings {
@Singleton
@Binds
abstract fun bindTest1(test1: Test1): ITest1
@Singleton
@Binds
abstract fun bindTest2(test2: Test2): ITest2
}
But I would like to do some configuration to classes before they are injected. To do so, I tried to use @Provide:
@Module(includes = [DependenciesBindings::class])
@InstallIn(SingletonComponent::class)
object Dependencies {
@Singleton
@Provides
fun provideTest1(): Test1 {
return Test1()
}
@Singleton
@Provides
fun provideTest2(): Test2 {
return Test2()
}
}
@Module
@InstallIn(SingletonComponent::class)
internal abstract class DependenciesBindings {
@Singleton
@Binds
abstract fun bindTest1(test1: Test1): ITest1
@Singleton
@Binds
abstract fun bindTest2(test2: Test2): ITest2
}
But this crashes app:
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property test1 has not been initialized at Test2.getTest1(Test2.kt:8) at Test2.doSomethingElse(Test2.kt:11)
Why this happens?
I would expect ITest1 to be correctly injected as Test1 is provided and then bind to ITest1 interface.
Is there a workaround to make this work?
By using a @Provides
method, you are constructing the instance yourself rather than using Dagger. Consequently, Dagger will allow you to do so without calling your @Inject
-annotated methods or populating your @Inject
-annotated fields, such that getTest1
will fail on the uninitialized field.
A more common pattern might have the bindings for ITest2 inject a Test2 instance (after all, @Provides
methods can take arguments from the graph), which allows Dagger to create the Test2 instance and allows you to manipulate it before returning:
@Module(/* no includes */)
@InstallIn(SingletonComponent::class)
object Dependencies {
@Singleton
@Provides
fun provideTest1(test1: Test1): ITest1 {
// manipulate test1
return test1
}
@Singleton
@Provides
fun provideTest2(test2: Test2): ITest2 {
// manipulate test2
return test2
}
}
If you cannot allow Dagger to create your Test1 or Test2 directly—for instance, if you don't have control over the constructor—you can inject a MembersInjector<Test1>
and use that to inject the @Inject
fields and methods.
@Module(includes = [DependenciesBindings::class])
@InstallIn(SingletonComponent::class)
object Dependencies {
@Singleton
@Provides
fun provideTest1(injector: MembersInjector<Test1>): Test1 {
val test1 = Test1()
injector.injectMembers(test1)
return test1
}
@Singleton
@Provides
fun provideTest2(injector: MembersInjector<Test2>): Test2 {
val test2 = Test2()
injector.injectMembers(test2)
return test2
}
}