2

I'm new to Dagger and have the following setup:

// data models
open class BaseEntity (open val id: Long)
data class UserEntity (override val id: Long, val name: String) : BaseEntity(id)
data class FruitEntity (override val id: Long, val status: String) : BaseEntity(id)

// interface to spec common API response operations
interface Repo<T> {
    fun getEntities(): List<T>
}

// entity specific implementation of the repo interface
class RepoImpl<T: BaseEntity> @Inject constructor(apiService: ApiService) : Repo<T> {
    override fun getEntities(): List<T> {
        val entities = apiService.getEntities(...)// get result from respective entity network service, e.g users, fruits etc
        return entities
    }
}

// DI: provide entity-specific implementations of the interface
@Singleton
@Provides
fun provideUserRepoImpl(userService: UserApiService): RepoImpl<BaseEntity> {
    return RepoImpl(userService)
}

@Singleton
@Provides
fun provideFruitRepoImpl(fruitService: FruitApiService): RepoImpl<BaseEntity> {
    return RepoImpl(fruitService)
}

When I build the project, this error comes up:

error: RepoImpl<com.example.data.model.BaseEntity> is bound multiple times
...
@org.jetbrains.annotations.NotNull @Provides @Singleton com.example.data.source.remote.RepoImpl<com.example.data.model.BaseEntity> com.example.app.di.AppModule.provideUserRepoImpl()
@org.jetbrains.annotations.NotNull @Provides @Singleton com.example.data.source.remote.RepoImpl<com.example.data.model.BaseEntity> com.example.app.di.AppModule.provideFruitRepoImpl()

And when I try to provide the entity-specific instances, like this:

@Singleton
@Provides
fun provideUserRepoImpl(userService: UserApiService): RepoImpl<UserEntity> {
    return RepoImpl(userService)
}

@Singleton
@Provides
fun provideFruitRepoImpl(fruitService: FruitApiService): RepoImpl<FruitEntity> {
    return RepoImpl(fruitService)
}

I get the following:

error: RepoImpl<com.example.data.model.BaseEntity> cannot be provided without an @Inject constructor or from an @Provides-annotated method

Also I have tried separating the provider methods of fruits & users into their respective modules but latter error also comes up, maybe I'm not hooking up the FruitModule, UserModule and AppModule correctly. I'm having similar problems providing implementations of interfaces.

What would be a correct approach here, or is it not at all possible to inject parametrized classes with Dagger?

UPDATE

I have managed to solve the dependency issue on RepoImpl by explicitly providing each object to be injected (the latter option where we provide RepoImpl<UserEntity> and RepoImpl<FruitEntity>). I think the @Inject annotation on the constructor was somehow not being registered, so had to refresh the project.

However, I cannot figure out how to provide implementations (or sub-types) of parametrized interfaces just yet. Consider for example the ApiService parameter of RepoImpl defined as follows:

// base service for all types of items
interface ApiService<T: BaseAttributes> {
    fun getEntities(): T
}
// service impl for fruits
class FruitService : ApiService<FruitAttributes> {
    override fun getEntities(): FruitResponses {...}
}
// service impl for users
class UserService : ApiService<UserAttributes> {
    override fun getEntities(): UserResponses {...}
}

// AppModule.kt:
@Singleton
@Provides
fun provideUserService() : ApiService<UserAttributes> {
    return UserService()
}

@Singleton
@Provides
fun provideFruitService() : ApiService<FruitAttributes> {
    return FruitService()
}

error: "... ApiService<BaseAttributes> cannot be provided without an @Provides-annotated method ..."

whereas

        ...
fun provideUserService() : ApiService<BaseAttributes> {
    return UserService() as ApiService<BaseAttributes>
}
        ...
fun provideFruitService() : ApiService<FruitAttributes> {
    return FruitService() as ApiService<BaseAttributes>
}

error: "... ApiService<BaseAttributes> is bound multiple times: ..."

How else can I inject these implementations of the interface?

kip2
  • 6,473
  • 4
  • 55
  • 72
  • Assume class `Foo` expects `ApiService` to be injected by dagger. And assume, that the implementation that you have provided works. What do you expect to happen, when `ApiService` is used: will it use `UserService` or `FruitService` as an implementation? Dagger is not doing magic for you. For a moment assume, that dagger does not exist. Which implementation will you provide as an argument to class `Foo`? – azizbekian May 17 '18 at 18:29
  • It depends on which instance of RepoImpl I needed: `val userRepo = RepoImpl(UserService())` or `val fruitRepo = RepoImpl(FruitService())`. What I'm looking for is the Dagger equivalent. I have an AppModule, UserModule and FruitModule, and not sure whether it matters which of the modules the @Provides methods should reside. I'm a total dagger noob so please forgive my current ignorance on the matter – kip2 May 17 '18 at 18:44
  • 1
    `It depends on which instance of RepoImpl I needed` So, currently you cannot answer to that question precisely, because it depends on some runtime type. Then how do you expect dagger to inject the dependency during compile time? See dagger `@Named` annotation usages, that might be what you are searching for. – azizbekian May 17 '18 at 18:47

1 Answers1

2

Turns out there's a well-discussed wildcard issue on dagger/kotlin generics. Particularly, the "cannot be provided without an @Provides-annotated method" error on parametrized classes requires suppressing generation of wildcard types (covariants or contravariants) with the @JvmSuppressWildcard annotation at the injection site. So I would have done:

class RepoImpl<T: BaseEntity> @Inject constructor(apiService: @kotlin.jvm.JvmSuppressWildcards ApiService) : Repo<T> { ... }

Though I actually ended up converting RepoImpl into an abstract class and creating concrete ones for Fruit & User types and providing them at the module-level instead:

class UserRepoImpl @Inject constructor(apiService: UserService) : RemoteRepoImpl(apiService) 
class FruitRepoImpl @Inject constructor(apiService: FruitService) : RemoteRepoImpl(apiService)

This related SO question contains another example.

Finally, this Kotlin+Dagger thread contains some nice gotchas and tips relevant to this problem

kip2
  • 6,473
  • 4
  • 55
  • 72