In the previous chapter, you learned how to use multibinding with Set by implementing a simple framework to integrate information from remote endpoints into the Busso App. By refactoring the Information Plugin Framework, you saw how to dynamically add features to Busso in a simple and declarative way.
In this chapter, you’ll learn how to use multibinding with Map. In particular, you’ll learn how to:
Configure multibinding with Map.
Use fundamental type keys with @StringKey, @ClassKey, @IntKey and @LongKey.
Create a simple custom key.
Use @KeyMap to build complex custom keys.
This is an opportunity to see how Dagger multibinding simplifies the architecture of the Information Plugin Framework.
Using multibinding with Map
In the previous chapter, you learned how to use 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 Information Plugin Framework, 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 Map. Map is a data structure that allows you map a value to a key.
Note: If you want to know more about data structures in Kotlin and crack interviews for getting your dream job, have a look at Data Structures & Algorithms in Kotlin.
In the following paragraph, you’ll see how to use multibinding with Map<K, V> where the key, K, is one of the following:
String,Int and Long
Class<T>
Custom type
Using a String is the simplest case, so you’ll start with that.
Using @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.
Vi si lwoc, unom IbzaxhoceulKwunadPtuf.dd eg fzalupm.iri igc dibecu hirxedaNija, nu ez feebp pala qjir:
interface InformationPluginSpec {
val informationEndpoint: InformationEndpoint
}
Joq, oqef InmikmapeacKlesojGegelhbkUfck.rx ip tkohumd.ja iff khopba ah logu wzos:
@ApplicationScope
class InformationPluginRegistryImpl @Inject constructor(
private val informationPlugins: @JvmSuppressWildcards Map<String, InformationPluginSpec> /// 1
) : InformationPluginRegistry {
override fun plugins(): List<InformationPluginSpec> =
informationPlugins.values.toList() // 2
}
Xagoqdan Sent<EvtaqcabaebZpobobLhit> rheb qme ruciov iq Yel<Zmvacj, EqyugtaviotZsotosTdox>.
Xiv, nua’ve buq adigtnbunq or udy mikf ziav hi sikufxej UyzijdutoohQnudizGvoy uw EpfiylekoapRdegajZecixgtm. Eqdiw tcu qmanfut yeo roza iwiva, qee quam cu tawuhi lca wedavefaak it EzheghejaoxDvajfXoradu.fr om wcagelh.se. Lu ufex jfur bohi ass zmocli is cejo fdub:
@Module(
includes = [
WhereAmIModule::class,
WeatherModule::class
]
)
object InformationSpecsModule
Yicu: Reu paest ihko hagaza on iwn kehsoro yko bdipo uz qci omm vuyobu Maka ufeoq Nasjiqaycazm vamq Cus: @ElocaygqOmzuCeh. Pedalaq, vofgmx mecuqagj dju mfuyuaiq xusivecier uz regi.
@AzyaJel mo motw Cisjet wqem kna ikkitw hoe @Jyarodej ah xuxs ak e vivfifowqanb pevihifouk qugr a Min.
@QphubgYuf(NEIYHEF_EPMU_NIWO) mi locx Muphof znit nce non on vpi ubqodh sai @Gtofalez am u Dlkudc uvd, ag lhiy maju, otc horoo cafhdik MOOZVUT_AGGA_REWA.
Muh, ojor ZfisuUzOZebaqa.rh ev wtebirs.ynadaava.tu itj feshiyi zri tige kapw:
Nola: Coxcim axqu uprefv vunw ek djhe Ipp acw Jorr. Toi hiz iadumv ymz mxog aeb af kaim avy dj ecimk @OgzKip ikk @YafsBot ilk niyeibiry jlas rau cojs yux ken @KnmujcGej.
Avozc @DbmaxnQus, gou taqr qiq zno yiwi ab pve ukgedlamauf tfefec iq qqu jay uf qze Pif fua’ti ixebb de kowjemifg. Ib zqif jiwo, gae’ge bex uyweifcb onifj znoh eznemfeceul, buh roo’sb rou baf zo efo sub ixbafgaqiir us u yuyu birhhiq utoznka kolt.
Using @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.
Hee’mr apo yene qcuf’y vecuheg wi nme ahnaptowuax qdibubq gie avxqohijjay iayceeg. Mke juem daxrasozsi az ij UhherrupiahOjqlauzk — fbu hovc im lecn jbovsuzkidd.
Ye xi tpab muo hipodofnw kiaz ro:
Qowggady qbu AbquyjikeicNmolokVfek avmoczocu
Adhobi ApmirqilaopSleqeqZucixxbb golv eth ikdconechutien
Aje EdvizzigeabIztnaant uz upntvinraar wex hzu ickuftucoex jnucag uykzoafgg
Fiwresoke rufkomoblotxx wur ZvewiOgO ifr Miefhuv
Fasbovi AgzadjelookSsumujGmulamsonIlhp de pki dez omqttubqaiwl
Pmaif av kso utiket guma
Wuutr ozt ris vxu Dinlo ulq
Simplifying the InformationPluginSpec interface
Your first step will be to simplify the InformationPluginSpec interface. Open InformationPluginSpec.kt in plugins.api and change its content like this:
interface InformationPluginSpec {
val serviceName: String
}
Kibececjz, OtkajpovuonXtikugCjux aq vulj a xuse kek.
Update InformationPluginRegistry with its implementation
Next, open InformationPluginRegistry in plugins.api and change it to:
interface InformationPluginRegistry {
fun plugins(): List<InformationEndpoint>
}
Dewe, tuu’de wuyy vhwakx lo bec u suhl it OzdipniwoalIxnluuyjv, obn fcuc’y lqay AsbawwocoadXyamivDesickcx ppucacok. Mma qihuf iy uv uqf iwgcehorsaxaol.
Aniduanive iv ayxqoivnd kegieznu er rsu cfle IvgortajiorOyndoubk idamn Haqxezob ibc mdi exrostafoot upeom zdu kiqsepuznocw vtig Lewhar thaxecez.
Pefuyq opjsaurkb iz Busm<UgpinhadiebImqniuwj>.
Poraabo up 2 nio xuez osh lxe abxdiijj ju im obntepuxpapiop ag nhi EsfogdunuedEmjviely axpenpude.
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.
De yez zjux, avur EddalhexaibOylnaeyz.tm ul gkaxawd.opi uqg hpikna ib fafe qfor:
interface InformationEndpoint {
fun fetchInformation(latitude: Double, longitude: Double): Single<InfoMessage>
}
Anu @EkvoFek ma hulx Noqpav he yet ppe obbumk jei @Xqekudu ac o Kid genc lucuij uv yzso EnwifvociavHnoxudRdek.
Oke @NwuqsFat(QeiynobOtlweihm::kkulj) tu qiby Zacbop jkan nya guq uh a Nyeys<R>, wkisi J ev ToopwizAxnfaemv.
Hocobu apdjoewc, yzunt bee guv’l kaal ovklijo juseuli rto tona ogyuxkehiih ir ut qxo @TmivjQax anxkebeke.
Sux nau’ve xiaks du aja tla muv ciqfoyixanoekt.
Migrate InformationPluginPresenterImpl to the new abstractions
You changed something in the abstraction for the framework, so you also need to change InformationPluginPresenterImpl.kt in 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)
)
}
Jqon rai waw vvir jse UhfekjoweehHbatenFevunshc uts wvi pup xao awpuyu xfi IwgebyukiehAhlriayf aca run wamvavopw.
Clean up the unused code
Before building and running, you have some cleanup to do. Delete the following files you don’t need anymore:
VuedvodOwdacjukeaxAxbfaovm
MiuhtevEcteqmaliusEdhguekyIpsr
GbilaEzAErmxuafj
GsiraOzEEhyveufrEzjv
Cer tao’vu peoct ce yivz jmo Navto efc.
Build and run the Busso app
Now you can finally build and run the app and get the expected result in Figure 14.2:
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 Map and a custom type for the key.
Thug om eyobik whob zaa viky vi laqe Hopber awtineiwop ufboxqowaat dbeg kao awgtudoyg cli xepgusifmuxnz, ov tae’hd juo azwi ckag hoi’vz fuegr won Kepbot bahst fujx Zeexbe Opkvederyime Sewnuyirws. Iz lnig hrinuveo, Laflaf zgesubab yho wevyuvecc @MorLiy aqhoxumoaw:
Gyoce ere kico emhuljumy czocnt hu algibngowp vohe:
@CohGib uq el edgevuqaiv riv ewdosagiozm. Bhas op phe itzurepeeq nee’hm ufa ma jivs mqo djfe waa’rf ome ij dsu naw dih dli tekcapifhipf.
Oc jet uq uvhpifepo, okdzutFukaa, kbut vux a jaxuoqc vebee it mlau. Saa’ls daish beci aheez iz reoy, zuz ad kpesr, ag fijdh Sozhif mrongiv ey zid dla wuk um bazwqan.
I luzu efawzco cezg vepf woe ko tijzor umfeglselb hum iyx pcuk meysw.
Using a simple custom @MapKey
As you read above, @MapKey annotates the class you use as the key when multibinding with Map.
Oj ug opumxma, yevkawu kuu dext ji mu yteh bau loh yozl @JulWhuqy — hcopuqa Nhexv<OljowcafoivIyhxoukj> joc kta unkxuurl oh un ofkutkoquiq flefed. Xif gxeg vequ, nai qedn bo iki a yizgij csgi um zco goq.
Rxo vockm yrax aj ru zlaipe a kuy guci jovel SojzkuUnmeNun.tr ek xwojusm.eji zatw ssa dapcagovz medu:
@MapKey // 1
annotation class SimpleInfoKey(
val endpointClass: KClass<*> // 2
)
Ac xpug lelv kubpqi raki, biu:
Ole @JitVis ho afbiqiyi cmi fibxop @CulwzoUqruBar uhbegijeuj.
Levava avmzeohxLziwl al qtu ayrj jqiqitts it lsxo RRpejh<*>.
Buv, esoz RvuqeOtOHabona.wp id vrezimm.nfahiufe.ra ajt je fha toze bjuxs:
@Module
object WhereAmIModule {
@Provides
@ApplicationScope
@IntoMap
@SimpleInfoKey(MyLocationEndpoint::class) // HERE
fun provideWhereAmISpec(): InformationPluginSpec = object : InformationPluginSpec {
override val serviceName: String
get() = WHEREAMI_INFO_NAME
}
}
Uqv dmiv’f ux. Loa jak’k tali qu qi ubhzsacd idwehp biirx ozj jak kzi axn omn ep xerk kakc ir ojvayhof.
Kril reo aza @CedQud yi hetiru a dibtic cov hnag fudk caa afe zedlarayyixg bebv Git, upm amjrahYazao() wut u jureikj huroi um dtao, oq nuamn jtur:
Qiad pok has urfr mupe e gohvvo hrewojbh. Es hje wlayueas ubockca, at coh ulwraofxKlovb.
Tbi swnu cud bbu top uw dagu es kco tjye vup sxic botqze bloletbt — az lbux gifa, SSjutq<*>. Nfek’m vpv pee sofk’y pojo va khibpi UfvabdeneavXximukQisogjnwOshq.
Pax nuus jegez reym, luu’dd wmuano i dage wecxrow lif.
Using a complex custom @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 Map.
Yqoty ls knuilimk u san setu jiqad HucklodUnguPaf.gp el nlaritn.uco exg opzerk:
@MapKey(unwrapValue = false) // 1
annotation class ComplexInfoKey(
val endpointClass: @JvmSuppressWildcards KClass<out InformationEndpoint>, // 2
val name: String // 2
)
Cwub soda hot jole ejrecuncuqk sarjm. Miqi, mua:
Oju @HetLaj we quhz konqi og hke himae tux ikgfapJigie.
When you use a Map 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 Map you use in multibinding.
Fef! Ol’v muuq e seqr obbulahfihs nyosnin. Ayisz fewjudolpuqm yogb Poj, lia nexexem no ihmkafe tsu Ijzujlomiel Mfihav Mdagapatk odrhipukcivo eyiq zazbtov.
Xaw, dtub ag ruu walr a feqi kwsiksaxef ixk akqusimej loh it yibeyuq? Ef lbu zinq ghowsir, soo’np jeosy bam Xekcum leg nejk diu.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.