Dagger 和 Kotlin - 将类绑定到其泛型超类型的问题
Posted
技术标签:
【中文标题】Dagger 和 Kotlin - 将类绑定到其泛型超类型的问题【英文标题】:Dagger and Kotlin - Issue with binding a class to its generic supertype 【发布时间】:2021-11-15 07:16:19 【问题描述】:我现在正用头撞墙,因为我想不通。
我有一个名为Mapper
的通用接口,它有两个通用类型参数。现在我想利用多重绑定并将这个接口的多个实现绑定到Map<Class<out Any>, Provider<Mapper<Any, Any>>
类型的映射中。我的代码如下所示:
interface Mapper<DTO, Entity>
fun toEntity(model: DTO): Entity
fun toDto(model: Entity): DTO
class PersistedIntakeEntryMapper @Inject constructor() : Mapper<PersistedIntakeEntry, IntakeEntry>
override fun toEntity(model: PersistedIntakeEntry): IntakeEntry TODO()
override fun toDto(model: IntakeEntry): PersistedIntakeEntry TODO()
@Module
interface MapperModule
@Binds
@IntoMap
@MapperKey(PersistedIntakeEntry::class)
@ModelMappers
fun bindPersistedIntakeEntryMapper(mapper: PersistedIntakeEntryMapper): Mapper<Any, Any>
@Singleton
class MapperFactory @Inject constructor(
@ModelMappers val mappers: Map<Class<out Any>, @JvmSuppressWildcards Provider<Mapper<Any, Any>>>,
)
@Suppress("UNCHECKED_CAST")
inline fun <reified DTO: Any, Entity> get(): Mapper<DTO, Entity>?
TODO()
Dagger 特别抱怨PersistedIntakeEntryMapper
不能分配给Mapper<Any, Any>
:MapperModule.java:13: error: @Binds methods' parameter type must be assignable to the return type
。
然而:奇怪的是,我对另一个像魅力一样工作的组件有相同的设置:
interface ViewModelFactory<VM : ViewModel, SavedState, Parameters>
fun create(savedState: SavedState?, parameters: Parameters?): VM
class SetCalorieGoalViewModelFactory @Inject constructor(
private val getCalorieGoalUseCase: GetCalorieGoalUseCase,
private val setCalorieGoalUseCase: SetCalorieGoalUseCase,
private val navigator: Navigator,
) : ViewModelFactory<SetCalorieGoalViewModel, SetCalorieGoalUiState, Nothing>
override fun create(savedState: SetCalorieGoalUiState?, parameters: Nothing?): SetCalorieGoalViewModel
TODO()
@Module
interface SetCalorieGoalUiModule
@Binds
@IntoMap
@ViewModelKey(SetCalorieGoalViewModel::class)
fun bindSetCalorieGoalViewModelFactory(factory: SetCalorieGoalViewModelFactory)
: ViewModelFactory<ViewModel, Any, Any>
我可以毫无问题地将SetCalorieGoalViewModelFactory
绑定到ViewModelFactory<SetCalorieGoalViewModel, Any, Any>
类型。这些设置使其中一个工作而另一个不工作的设置之间有什么区别?我这辈子都想不通。非常感谢任何试图解决此问题的人。
【问题讨论】:
我认为是因为interface Mapper<DTO, Entity>
中的类型没有定义。意思是,它也可以为空。所以制作这个interface Mapper<DTO : Any, Entity : Any>
看看它是否有效
好声,感谢您的回复,但不幸的是它没有帮助,仍然是同样的问题。
【参考方案1】:
首先,查看kotlin documentation 的泛型方差主题以及相关的 java 主题(因为 dagger 生成 java 代码)。
通常问题是Mapper<PersistedIntakeEntry, IntakeEntry>
和Mapper<Any, Any>
是不变的,这意味着一个不是另一个的子类型。基本上这个赋值 val mapper: Mapper<Any, Any> = PersistedIntakeEntryMapper()
不会编译,这就是 dagger 告诉你的。这是有道理的,因为Mapper<Any, Any>
必须能够将Any
映射到Any
,而PersistedIntakeEntryMapper
显然不是这种情况——它需要PersistedIntakeEntry
和IntakeEntry
。
按照上面的文档,如果您的声明指定了out
修饰符,如interface Mapper<out DTO, out Entity>
,则可能,但这不适用于您的情况,因为您的类型参数位于in
位置。
有趣的问题是为什么它适用于ViewModelFactory
。这似乎是 KAPT 中的一个错误,当它看到Nothing
时,它只是在生成的代码中省略了泛型类型参数。它使它绕过编译器检查(但它不能在运行时安全使用!),因为泛型主要是编译时的东西(参见 type erasure in java)。
【讨论】:
感谢周到的回复,让我明白了很多。这里有趣的事情绝对是它与 ViewModelFactory 一起工作的原因,因为将SetCalorieGoalViewModelFactory
分配给类型 ViewModelFactory<Any, Any, Any>
也会导致编译器错误。所以绝对有理由假设这是由于 kapt 中的一个错误。由于我在MapperFactory
中进行了未经检查的强制转换,因此代码无论如何都不是运行时安全的,所以我解决这个问题的方法是引入一个非通用的BaseMapper
接口来绑定特定的映射器。以上是关于Dagger 和 Kotlin - 将类绑定到其泛型超类型的问题的主要内容,如果未能解决你的问题,请参考以下文章
dagger2 和 kotlin 的 Android 单元测试问题
Kotlin、Java、multidex、Dagger 2、Butterknife 和 Realm:transformClassesWithJarMergingForDebug:重复条目:org/je
未解决的参考:DaggerTestComponent(Kotlin with Dagger for Test)