匕首:如何提供参数化基本类型的特定实现



我是dagger的新手,有以下设置:

// 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)
}

当我构建项目时,出现此错误:

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()

当我尝试提供特定于实体的实例时,如下所示:

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

我得到以下信息:

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

我也尝试将水果和用户的提供程序方法分离到各自的模块中,但后来的错误也出现了,也许我没有正确连接FruitModule,UserModule和AppModule。我在提供接口实现时遇到了类似的问题。

这里正确的方法是什么,或者根本不可能用 Dagger 注入参数化类?

更新

我已经设法通过显式提供要注入的每个对象来解决对 RepoImpl 的依赖问题(我们提供RepoImpl<UserEntity>RepoImpl<FruitEntity>的后一个选项)。我认为构造函数上的@Inject注释不知何故没有注册, 所以不得不刷新项目。

但是,我还无法弄清楚如何提供参数化接口的实现(或子类型)。 例如,考虑RepoImpl的ApiService参数,定义如下:

// 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()
}

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

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

错误:"... ApiService<BaseAttributes> is bound multiple times: ..."

我还能如何注入接口的这些实现?

事实证明,在dagger/kotlin 泛型上有一个经过充分讨论的通配符问题。特别是,参数化类上的"没有@Provides注释方法就无法提供"错误需要在注入位置使用@JvmSuppressWildcard注释抑制通配符类型(协变或逆变)的生成。所以我会这样做:

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

虽然我实际上最终将RepoImpl转换为抽象类,并为Fruit&User类型创建具体的类,并在模块级别提供它们:

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

这个相关的SO问题包含另一个示例。

最后,这个 Kotlin+Dagger 线程包含一些与此问题相关的不错的陷阱和提示

最新更新