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&lt;Any, Any&gt;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&lt;SetCalorieGoalViewModel, Any, Any&gt; 类型。这些设置使其中一个工作而另一个不工作的设置之间有什么区别?我这辈子都想不通。非常感谢任何试图解决此问题的人。

【问题讨论】:

我认为是因为interface Mapper&lt;DTO, Entity&gt;中的类型没有定义。意思是,它也可以为空。所以制作这个interface Mapper&lt;DTO : Any, Entity : Any&gt; 看看它是否有效 好声,感谢您的回复,但不幸的是它没有帮助,仍然是同样的问题。 【参考方案1】:

首先,查看kotlin documentation 的泛型方差主题以及相关的 java 主题(因为 dagger 生成 java 代码)。

通常问题是Mapper&lt;PersistedIntakeEntry, IntakeEntry&gt;Mapper&lt;Any, Any&gt; 是不变的,这意味着一个不是另一个的子类型。基本上这个赋值 val mapper: Mapper&lt;Any, Any&gt; = PersistedIntakeEntryMapper() 不会编译,这就是 dagger 告诉你的。这是有道理的,因为Mapper&lt;Any, Any&gt; 必须能够将Any 映射到Any,而PersistedIntakeEntryMapper 显然不是这种情况——它需要PersistedIntakeEntryIntakeEntry

按照上面的文档,如果您的声明指定了out 修饰符,如interface Mapper&lt;out DTO, out Entity&gt;,则可能,但这不适用于您的情况,因为您的类型参数位于in 位置。

有趣的问题是为什么它适用于ViewModelFactory。这似乎是 KAPT 中的一个错误,当它看到Nothing 时,它只是在生成的代码中省略了泛型类型参数。它使它绕过编译器检查(但它不能在运行时安全使用!),因为泛型主要是编译时的东西(参见 type erasure in java)。

【讨论】:

感谢周到的回复,让我明白了很多。这里有趣的事情绝对是它与 ViewModelFactory 一起工作的原因,因为将 SetCalorieGoalViewModelFactory 分配给类型 ViewModelFactory&lt;Any, Any, Any&gt; 也会导致编译器错误。所以绝对有理由假设这是由于 kapt 中的一个错误。由于我在MapperFactory 中进行了未经检查的强制转换,因此代码无论如何都不是运行时安全的,所以我解决这个问题的方法是引入一个非通用的BaseMapper 接口来绑定特定的映射器。

以上是关于Dagger 和 Kotlin - 将类绑定到其泛型超类型的问题的主要内容,如果未能解决你的问题,请参考以下文章

dagger2 和 kotlin 的 Android 单元测试问题

kotlin 缺少提供程序的 Dagger 2 错误

Kotlin、Java、multidex、Dagger 2、Butterknife 和 Realm:transformClassesWithJarMergingForDebug:重复条目:org/je

未解决的参考:DaggerTestComponent(Kotlin with Dagger for Test)

错误:error.NonExistentClass Kotlin 在多模块 Dagger 项目中

组件(无作用域)可能不引用作用域绑定