Dagger 和 Kotlin - 将 class 绑定到其通用超类型的问题
Dagger and Kotlin - Issue with binding a class to its generic supertype
我现在正在用头撞墙,因为我想不通。
我有一个名为 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>
类型。使其中一个工作而另一个不工作的这些设置之间有什么区别?我一辈子都弄不明白。非常感谢任何试图解决这个问题的人。
首先,查看 kotlin documentation 通用方差主题以及相关的 java 主题(因为匕首生成 java 代码)。
一般来说,问题是 Mapper<PersistedIntakeEntry, IntakeEntry>
和 Mapper<Any, Any>
是不变的,这意味着一个不是另一个的子类型。基本上这个赋值 val mapper: Mapper<Any, Any> = PersistedIntakeEntryMapper()
不会编译,这就是 dagger 告诉你的。这是有道理的,因为 Mapper<Any, Any>
必须能够将 Any
映射到 Any
而 PersistedIntakeEntryMapper
显然不是这种情况 - 它期望 PersistedIntakeEntry
和 IntakeEntry
.
按照上面的文档,如果你的声明有像 interface Mapper<out DTO, out Entity>
这样指定的 out
修饰符,那将是可能的,但这在你的情况下不起作用,因为你的类型参数在 in
个职位。
有趣的问题是为什么它适用于 ViewModelFactory
。这似乎是 KAPT 中的一个错误,它只是在看到 Nothing
时在生成的代码中省略了泛型类型参数。它使它绕过编译器检查(但在运行时使用它并不安全!),因为泛型主要是编译时的东西(参见 java 中的 type erasure ).
我现在正在用头撞墙,因为我想不通。
我有一个名为 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>
类型。使其中一个工作而另一个不工作的这些设置之间有什么区别?我一辈子都弄不明白。非常感谢任何试图解决这个问题的人。
首先,查看 kotlin documentation 通用方差主题以及相关的 java 主题(因为匕首生成 java 代码)。
一般来说,问题是 Mapper<PersistedIntakeEntry, IntakeEntry>
和 Mapper<Any, Any>
是不变的,这意味着一个不是另一个的子类型。基本上这个赋值 val mapper: Mapper<Any, Any> = PersistedIntakeEntryMapper()
不会编译,这就是 dagger 告诉你的。这是有道理的,因为 Mapper<Any, Any>
必须能够将 Any
映射到 Any
而 PersistedIntakeEntryMapper
显然不是这种情况 - 它期望 PersistedIntakeEntry
和 IntakeEntry
.
按照上面的文档,如果你的声明有像 interface Mapper<out DTO, out Entity>
这样指定的 out
修饰符,那将是可能的,但这在你的情况下不起作用,因为你的类型参数在 in
个职位。
有趣的问题是为什么它适用于 ViewModelFactory
。这似乎是 KAPT 中的一个错误,它只是在看到 Nothing
时在生成的代码中省略了泛型类型参数。它使它绕过编译器检查(但在运行时使用它并不安全!),因为泛型主要是编译时的东西(参见 java 中的 type erasure ).