【问题标题】:Not able to inject a multi-binding with kotlin and dagger无法使用 kotlin 和 dagger 注入多重绑定
【发布时间】:2019-03-29 22:54:35
【问题描述】:

我有以下定义:

@Module
class WeaverDataModule {
    // Provide the three pumps from providers
    // All of them still explicitly mark 'Pump' as their return type
    @Provides @IntoSet fun providesPump(thermosiphon: Thermosiphon) : Pump = thermosiphon
    @Provides @IntoSet fun providesAnotherPump(suctionBased: SuctionBased) : Pump = suctionBased
    @Provides @IntoSet fun providesGenericPump(genericPump: GenericPump) : Pump = genericPump
}

@Component(modules = [WeaverDataModule::class])
interface WeaverData {
    // Get the CoffeeMaker
    fun coffeeMaker(): CoffeeMaker
    // Get the list of pumps
    fun getPumps() : Set<Pump>
}

interface Pump

// The three pumps
class Thermosiphon @Inject constructor(val heater: Heater) : Pump
class SuctionBased @Inject constructor() : Pump
class GenericPump @Inject constructor() : Pump

// Some random heater
class Heater @Inject constructor()

在我的代码中,当我执行以下操作时:

val cm = DaggerWeaverData.builder().build().getPumps()

我确实按预期得到了三个泵。但是,当我尝试将其注入其他类时:

class CoffeeMaker @Inject constructor(
    private val heater: Heater,
    private val pump: Set<Pump>
) {
    fun makeCoffee() =
        "Making coffee with heater ${heater::class.java} and using pumps" +
                " ${pump.map { it::class.java }.joinToString(",")}"
}

我收到以下错误:

e: .../WeaverData.java:7: error: [Dagger/MissingBinding] java.util.Set<? extends weaver.Pump> cannot be provided without an @Provides-annotated method.                    
public abstract interface WeaverData {
                ^
      java.util.Set<? extends weaver.Pump> is injected at
          weaver.CoffeeMaker(…, pump)
      weaver.CoffeeMaker is provided at
          weaver.WeaverData.coffeeMaker()

我也尝试过注入Collection&lt;Pump&gt;,但我仍然遇到类似的错误。在dagger docs on multibinding 中,示例(Java 中)显示如下:

class Bar {
  @Inject Bar(Set<String> strings) {
    assert strings.contains("ABC");
    assert strings.contains("DEF");
    assert strings.contains("GHI");
  }
}

这正是我正在做的。对于基于构造函数的注入,它在 Kotlin 中工作得很好,因为以下编译并按预期运行:

class CoffeeMaker @Inject constructor(
    private val heater: Heater
) {
    fun makeCoffee() =
        "Making coffee with heater ${heater::class.java}"
}

所以我有点不知道如何让这种多重绑定发挥作用。

【问题讨论】:

    标签: kotlin dagger-2


    【解决方案1】:

    原来你需要做的是:

    class CoffeeMaker @Inject constructor(
        private val heater: Heater,
        private val pumps: Set<@JvmSuppressWildcards Pump>
    ) {
        fun makeCoffee() =
            "Making coffee with heater ${heater::class.java} with pumps ${pumps.map { it::class.java }.joinToString(",")}"
    }
    

    这是因为 Set 在 Kotlin 中被定义为 Set&lt;out E&gt;,它在 Java 中被定义为 Set&lt;? extends Pump&gt;。从类型论的角度来看,Set&lt;? extends Pump&gt;Set&lt;Pump&gt; 不同,因此 Dagger(可能)拒绝将 Set&lt;Pump&gt; 视为 Set&lt;? extends Pump&gt; 的可注入物,这是公平且正确的行为。

    我们遇到的问题是,对于这些集合中的任何一个,由于默认情况下它们是不可变的,因此 Set&lt;X&gt; 类型的声明将转换为 Set&lt;? extends X&gt;,因为不可变集合仅在返回时引用已解析的类型,并且因此是协变的。为了验证这一理论,以下方法也有效:

    class CoffeeMaker @Inject constructor(
        private val heater: Heater,
        private val pumps: MutableSet<Pump>
    ) {
        fun makeCoffee() =
            "Making coffee with heater ${heater::class.java} with pumps ${pumps.map { it::class.java }.joinToString(",")}"
    }
    

    注意MutableSet的使用,它被定义为MutableSet&lt;E&gt; : Set&lt;E&gt; ...。这可能不是一个应该使用的东西,因为我怀疑这个集合实际上是可变的。所以我们需要的是让 kotlin 编译器将Set&lt;out E&gt; 视为Set&lt;E&gt; (在这种情况下assignabiliy 是有效的,而不是相反)。所以这样做,我们使用@JvmSuppressWildcards 注解。我希望这可以帮助其他面临类似问题的人。

    【讨论】:

    • 我一直使用MutableSet&lt;T&gt; 和匕首作为一种解决方法,但对此并不满意,我想我会尝试纠正这个明显的缺陷。请注意,@JvmSuppressWildcards 必须包含在所有使用 Set&lt;T&gt; 的地方 - 从可注入类、注入类,尤其是在使用 @Binds 时。此外,如果您有很多这样的注入参数,您可以将@JvmSuppressWildcards 传播到该类并且只包含一次。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-30
    • 2022-01-11
    • 1970-01-01
    • 2019-10-27
    • 2019-07-04
    • 1970-01-01
    相关资源
    最近更新 更多