Reputation: 6432
I have a simple interface and class:
interface Foo {
fun value(): Int
}
class FooImpl : Foo {
override fun value() = 100
}
Now I want to create a factory for Foo
and be able to inject it. I'm trying with following code:
interface FooFactory : () -> Foo
@Module
class AppModule {
// provides FooFactory
@Provides
fun provideFooFactory() = object : FooFactory {
override fun invoke() = FooImpl()
}
// uses FooFactory
@Provides
fun provideFoo(factory: FooFactory) = factory()
}
@Component(modules = [AppModule::class])
interface AppComponent {
fun foo(): Foo
}
And the place where Foo
is injected:
@Test
fun test() {
val component = DaggerAppComponent.builder().build()
val foo = component.foo()
Assert.assertEquals(100, foo.value())
}
Works perfect! However, I think, it's a kind of ugly to define FooFactory
as an interface so I tried to replace:
interface FooFactory : () -> Foo
with:
typealias FooFactory = () -> Foo
And now I'm getting compile time error:
Error:Gradle:
kotlin.jvm.functions.Function0<? extends net.chmielowski.daggerkotlin.Foo>
cannot be provided without an @Provides-annotated method.
If I understand it correctly, the problem is that typealias
is inlined in the process of build (before Dagger code generation) and Dagger has a problem with finding out which provider provides instance for parametrized (generic) type Function0<? extends Foo>
.
By the way: if I remove Foo
and use just FooImpl
everywhere, the problem does not occur. This mean that the problem is not with the generics itself but with the class parametrized by abstract type.
What is the solution to this problem?
To be clear - the rationale behind using typealias
instead of interface
is to be able to write:
@Provides
fun provideFooFactory() = { FooImpl() }
instead of:
@Provides
fun provideFooFactory() = object : FooFactory {
override fun invoke() = FooImpl()
}
Upvotes: 3
Views: 1101
Reputation: 1827
I had a similar issue today where I was using a kotlin lambda to return a value through dagger.
DaggerModule.kt
@Provides
fun provideFoo() = {
foo.get()
}
BarClass.kt
BarClass( private val fooFunction: () -> Foo )
I was getting the same error you were seeing where dagger couldn't find the provided method.
(as above)
kotlin.jvm.functions.Function0<? extends net.chmielowski.daggerkotlin.Foo>
cannot be provided without an @Provides-annotated method.
For me it was not enough to explicitly declare the type in the provider. The solution instead had to do with how kotlin and java deal with generics. Basically when translating that function into Java (which dagger still does under the hood) it was adding < ? extends Foo> to the method signature. You can suppress this behavior with the @JvmSuppressWildcards annotation in the constructor of the receiving class.
BarClass( private val fooFunction: () -> @JvmSuppressWildcards Foo )
Upvotes: 3
Reputation: 39853
The implementation
@Provides fun provideFooFactory() = { FooImpl() }
implies a return type of () -> FooImpl
which would translate to Function0<? extends FooImpl>
which dagger cannot match to Function0<? extends Foo>
. Instead you have to make sure provideFooFactory
has the correct return type.
This you can do by down-casting
@Provides fun provideFooFactory() = { FooImpl() as Foo }
or explicitly declaring the type
@Provides fun provideFooFactory(): FooFactory = { FooImpl() }
Upvotes: 0