首页 安卓& Kotlin Books 匕首by Tutorials

14
用地图多筹码 由Massimo Carli撰写

在上一章中,您学会了如何使用 与套装多弯曲 通过实现一个简单的框架,将来自远程端点的信息集成到BUSSO应用程序中。通过重构 信息插件框架,您看到如何以简单且声明的方式动态地将功能添加到BusSo。

图14.1  - 信息插件框架
图14.1 - 信息插件框架

在本章中,您’ll learn how to use multibinding with 地图. In particular, you’ll learn how to:

  • Configure multibinding with 地图.
  • Use fundamental type keys with @StringKey, @ClassKey, @IntKey and @LongKey.
  • 创建一个简单的自定义密钥。
  • Use @KeyMap to build complex custom keys.

这是一个有机会看到匕首多弯曲如何简化的架构 信息插件框架.

使用带有地图的多元inding

在上一章中,您学会了如何使用 multibinding with Sets. Set is an unordered data structure that lets you avoid duplicates. This is great but sometimes you might need something different. For instance, in the case of the 信息插件框架, a Set doesn’t allow you to decide the order of the information to display on the screen.

In any case, Dagger offers you another option: multibinding with 地图. 地图 is a data structure that allows you map a value to a key.

笔记:如果您想了解更多有关Kotlin中的数据结构以及让您的梦想工作的裂缝访谈,请看看 数据结构&Kotlin的算法.

在以下段落中,您将看到如何使用多indIning 地图<K, V> 关键的地方, K,是以下之一:

  • String,Int and Long
  • Class<T>
  • 自定义类型

Using a String is the simplest case, so you’ll start with that.

使用@StringKey.

For your first example, suppose you want to simplify InformationPluginSpec by removing the property name and giving the plugin a name when you add it to the registry.

在terface InformationPluginSpec {

  val informationEndpoint: InformationEndpoint
}
@ApplicationScope
class 信息普通话Impl @Inject constructor(
    private val informationPlugins: @JvmSuppressWildcards Map<String, InformationPluginSpec> /// 1
) : 信息普通话 {

  override fun plugins(): List<InformationPluginSpec> =
      informationPlugins.values.toList() // 2
}
@Module(
    includes = [
      WhereAmIModule::class,
      WeatherModule::class
    ]
)
object InformationSpecsModule
const val WEATHER_INFO_NAME = "Weather"
@Module(includes = [WeatherModule.Bindings::class])
object WeatherModule {
  @Provides
  @ApplicationScope
  @IntoMap // 1
  @StringKey(WEATHER_INFO_NAME) // 2
  fun provideWeatherSpec(endpoint: WeatherInformationEndpoint): InformationPluginSpec = object : InformationPluginSpec {
    override val informationEndpoint: InformationEndpoint
      get() = endpoint
  }
  // ...
}
const val WHEREAMI_INFO_NAME = "WhereAmI"
@Module(includes = [WhereAmIModule.Bindings::class])
object WhereAmIModule {
  // ...
  @Provides
  @ApplicationScope
  @IntoMap
  @StringKey(WHEREAMI_INFO_NAME)
  fun provideWhereAmISpec(endpoint: WhereAmIEndpointImpl): InformationPluginSpec = object : InformationPluginSpec {
    override val informationEndpoint: InformationEndpoint
      get() = endpoint
  }
}

使用@classkey.

Another type of key Dagger gives you is Class<T>. You can use it the same way you used String, but for your next step, you’ll try something more ambitious, instead.

简化信息Pluginspec接口

Your first step will be to simplify the InformationPluginSpec interface. Open InformationPluginspec.kt.plugins.api. 并更改其内容如下:

在terface InformationPluginSpec {
  val serviceName: String
}

更新信息普通内容及其实施

接下来,开放 信息普通话plugins.api. and change it to:

在terface 信息普通话 {
  fun plugins(): List<InformationEndpoint>
}
@ApplicationScope
class 信息普通话Impl @Inject constructor(
    private val retrofit: Retrofit, // 1
    informationPlugins: @JvmSuppressWildcards Map<Class<*>, InformationPluginSpec> // 2
) : 信息普通话 {

  val endpoints = informationPlugins.keys.map { clazz ->
    retrofit.create(clazz) // 3
  }.map { endpoint ->
    endpoint as InformationEndpoint
  }.toList()

  override fun plugins(): List<InformationEndpoint> = endpoints // 4
}

Use InformationEndpoint as abstraction for the information plugin endpoints

Look at MyLocationEndpoint and WeatherEndpoint and notice there’s a problem — they don’t share any abstraction. The implementations Retrofit creates for you aren’t InformationEndpoint implementations, and they all define operations with different names and parameters.

在terface InformationEndpoint {

  fun fetchInformation(latitude: Double, longitude: Double): Single<InfoMessage>
}
在terface MyLocationEndpoint : InformationEndpoint { // 1
  @GET("${BUSSO_SERVER_BASE_URL}myLocation/{lat}/{lng}")
  override fun fetchInformation( // 2
      @Path("lat") latitude: Double,
      @Path("lng") longitude: Double
  ): Single<InfoMessage>
}
在terface WeatherEndpoint : InformationEndpoint {

  @GET("${BUSSO_SERVER_BASE_URL}weather/{lat}/{lng}")
  override fun fetchInformation(
      @Path("lat") latitude: Double,
      @Path("lng") longitude: Double
  ): Single<InfoMessage>
}

为Whereami和天气配置多吲哚

Now it’s time to configure multibinding with 地图 for the information plugins. Open whereamimodule.kt.plugins.whereami.di. 并将其更改为此:

const val WHEREAMI_INFO_NAME = "WhereAmI"
@Module
object WhereAmIModule {
  @Provides
  @ApplicationScope
  @IntoMap // 1
  @ClassKey(MyLocationEndpoint::class) // 2
  fun provideWhereAmISpec():
      InformationPluginSpec = object : InformationPluginSpec { // 3
    override val serviceName: String
      get() = WHEREAMI_INFO_NAME
  }
}
const val WEATHER_INFO_NAME = "Weather"
@Module
object WeatherModule {
  @Provides
  @ApplicationScope
  @IntoMap // 1
  @ClassKey(WeatherEndpoint::class) // 2
  fun provideWeatherSpec():
      InformationPluginSpec = object : InformationPluginSpec { // 3
    override val serviceName: String
      get() = WEATHER_INFO_NAME
  }
}

Migrate InformationPluginPresenterImpl to the new abstractions

您在框架的抽象中更改了一些东西,因此您还需要更改 InformationPluginPresenterimpl.kt.plugins.ui.. Just replace the start() implementation with the following:

  override fun start() {
    disposables.add(
        locationObservable.filter(::isLocationEvent)
            .map { locationEvent ->
              locationEvent as LocationData
            }
            .firstElement()
            .map { locationData ->
              val res = informationPluginRegistry.plugins().map { endpoint ->
                val location = locationData.location
                endpoint.fetchInformation(location.latitude, location.longitude) // HERE
                    .toFlowable()
              }
              Flowable
                  .merge(res)
                  .collectInto(mutableListOf<String>()) { acc, item ->
                    acc.add(item.message)
                  }
            }
            .subscribe(::manageResult, ::handleError)
    )
  }

清理未使用的代码

在建造和运行之前,您可以进行一些清理。删除以下文件您不再需要:

构建并运行BUSSO应用程序

现在,您最终可以构建和运行应用程序并获得图14.2的预期结果:

图14.2  -  Busso应用程序按预期工作
Cikiwa 55.5 - vwe mibya izz womlf,例如iwjeqcoq

使用带有自定义密钥的多indIning

You can usually cover all the use cases you encounter by using @StringKey and @ClassKey. But just in case you need something special, Dagger offers multibinding with 地图 and a custom type for the key.

@Documented
@Target(ANNOTATION_TYPE) // 1
@Retention(RUNTIME)
public @interface MapKey {

  boolean unwrapValue() default true; // 2
}

使用简单的自定义@mapkey

As you read above, @MapKey annotates the class you use as the key when multibinding with 地图.

@MapKey // 1
annotation class SimpleInfoKey(
    val endpointClass: KClass<*> // 2
)
@Module
object WeatherModule {

  @Provides
  @ApplicationScope
  @IntoMap
  @SimpleInfoKey(WeatherEndpoint::class) // HERE
  fun provideWeatherSpec(): InformationPluginSpec = object : InformationPluginSpec {
    override val serviceName: String
      get() = WEATHER_INFO_NAME
  }
}
@Module
object WhereAmIModule {

  @Provides
  @ApplicationScope
  @IntoMap
  @SimpleInfoKey(MyLocationEndpoint::class) // HERE
  fun provideWhereAmISpec(): InformationPluginSpec = object : InformationPluginSpec {
    override val serviceName: String
      get() = WHEREAMI_INFO_NAME
  }
}

使用复杂的自定义@mapkey

Your final step is to make the InformationPluginSpec definition redundant and instead, put all the information you need in a custom key to use with multibinding and 地图.

@MapKey(unwrapValue = false) // 1
annotation class ComplexInfoKey(
    val endpointClass: @JvmSuppressWildcards KClass<out InformationEndpoint>, // 2
    val name: String // 2
)
  implementation "com.google.auto.value:auto-value-annotations:$autovalue_annotation_version"
  kapt "com.google.auto.value:auto-value:$autovalue_version"
@Module
object WeatherModule {

  @Provides
  @ApplicationScope
  @IntoMap
  @ComplexInfoKey( // 1
      WeatherEndpoint::class,
      WEATHER_INFO_NAME
  )
  fun provideWeatherSpec(): InformationPluginSpec = InformationPluginSpec
}
object InformationPluginSpec
@Module
object WhereAmIModule {

  @Provides
  @ApplicationScope
  @IntoMap
  @ComplexInfoKey(
      MyLocationEndpoint::class,
      WHEREAMI_INFO_NAME
  )
  fun provideWhereAmISpec(): InformationPluginSpec = InformationPluginSpec
}
@ApplicationScope
class 信息普通话Impl @Inject constructor(
    private val retrofit: Retrofit,
    informationPlugins: @JvmSuppressWildcards Map<ComplexInfoKey, InformationPluginSpec> // 1
) : 信息普通话 {

  val endpoints = informationPlugins.keys.map { complexKey ->
    retrofit.create(complexKey.endpointClass.java as Class<*>) // 2
  }.map { endpoint ->
    endpoint as InformationEndpoint
  }.toList()

  override fun plugins(): List<InformationEndpoint> = endpoints
}
图14.3  -  BUSSO应用程序按预期工作
DoxoTo 33.3 - ZGI Diyha Ash Befvm UX Ayqonnap

关键点

  • 匕首allows you to use multibinding with a 地图.
  • When you use a 地图 for multibinding, you can use keys of the following types: String, Int, Long and KClass.
  • If you need more informative keys, @KeyMap allows you to create custom types, which you can use in a simple or complex way.
  • If you use @KeyMap and an unwrapValue attribute with a default value of true, the type of the key is the type of the unique property of your custom key.
  • A @KeyMap is complex if the value for the unwrapValue attribute is false. In this case, you need to add an auto-value as a dependency in your project.
  • You must use a complex @KeyMap for the key of the 地图 you use in multibinding.

有一个技术问题?想报告一个错误吗? 您可以向官方书籍论坛中的书籍作者提出问题和报告错误 这里.

有反馈分享在线阅读体验吗? 如果您有关于UI,UX,突出显示或我们在线阅读器的其他功能的反馈,您可以将其发送到设计团队,其中表格如下所示:

© 2021 Razeware LLC

您可以免费读取,本章的部分显示为 混淆了 文本。解锁这本书,以及我们整个书籍和视频目录,带有Raywenderlich.com的专业订阅。

现在解锁

要突出或记笔记,您需要在订阅中拥有这本书或自行购买。