Injecting typeliased class parametrized by abstract type

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

Answers (2)

Travis Castillo
Travis Castillo

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

tynn
tynn

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

Related Questions