【问题标题】:Dagger: How to provide specific implementation of a parametrized base type?Dagger:如何提供参数化基类型的具体实现?
【发布时间】:2018-05-17 11:26:33
【问题描述】:

我是 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&lt;UserEntity&gt;RepoImpl&lt;FruitEntity&gt; 的后一个选项),我设法解决了对 RepoImpl 的依赖问题。我认为构造函数上的 @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&lt;BaseAttributes&gt; 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&lt;BaseAttributes&gt; is bound multiple times: ..."

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

【问题讨论】:

  • 假设类Foo 期望ApiService&lt;BaseAttributes&gt; 被dagger 注入。并假设您提供的实现有效。当使用ApiService 时,您期望会发生什么:它会使用UserServiceFruitService 作为实现吗?匕首不是为你做魔法。暂时假设,那把匕首不存在。您将提供哪个实现作为Foo 类的参数?
  • 这取决于我需要哪个 RepoImpl 实例:val userRepo = RepoImpl(UserService())val fruitRepo = RepoImpl(FruitService())。我正在寻找的是 Dagger 等价物。我有一个 AppModule、UserModule 和 FruitModule,但不确定 @Provides 方法应该驻留在哪个模块中是否重要。我是一个十足的匕首菜鸟,所以请原谅我目前对此事的无知
  • It depends on which instance of RepoImpl I needed 因此,目前您无法准确回答该问题,因为它取决于某些运行时类型。那么你如何期望 dagger 在编译时注入依赖呢?请参阅 dagger @Named 注释用法,这可能就是您要搜索的内容。

标签: android dependency-injection kotlin dagger-2 dagger


【解决方案1】:

原来在 dagger/kotlin 泛型上有一个 well-discussed wildcard issue。特别是,参数化类上的“不能在没有@Provides-annotated 方法的情况下提供”错误需要在注入站点使用@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)

related SO question 包含另一个示例。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多