In the previous chapter, you learned about combining operators and worked through increasingly more detailed exercises on some rather mind-bending concepts. Some operators may have left you wondering about the real-world application of these reactive concepts.
In this “… in practice” chapter, you’ll have the opportunity to try some of the most powerful operators. You’ll learn how to solve problems similar to those you’ll face in your own applications.
Note: This chapter assumes you’ve already worked your way through Chapter 9, “Combining Operators,” and are familiar with both filtering (Chapter 5) and transforming operators (Chapter 7). At this point in the book, it is important that you are familiar with these concepts, so make sure to review these chapters if necessary!
You’ll start with a new project for this chapter and build a small application with an ambitious name: Our Planet.
Getting started
The project will tap into the wealth of public data exposed by NASA. You’ll target EONET, NASA’s Earth Observatory Natural Event Tracker. It is a near real-time, curated repository of natural events of all types occurring on the planet. Check out https://eonet.sci.gsfc.nasa.gov/ to learn more!
To get started with Our Planet, open the starter project for this chapter in Android Studio 4.0 or newer.
Build and run the starter application; the default screen is empty.
Download events and show a count for each category.
When user taps a category, display a list of events for this category.
You’ll learn how useful combineLatest can be in several situations, but you’ll also exercise concat, merge, and scan. Of course, you’ll also rely on operators you are already familiar with, like map and flatMap.
Preparing the EONET API class
Good applications have a clear architecture with well-defined roles. The code that talks with the EONET API shouldn’t live in any of the activities. Instead, it will live in an object that you’ll reference from a ViewModel.
Ezmavd vxu huvuq pevtumu ad dce EitTtikow qbudeyf; dua’by xadj i Gibwesok abmavtuzi piunl buw dpi efc su abetofe az IOCOQAvu.bw. Rnop imbakciti mesg si aqaf jw wqu OESUP ojminj. Rae’qg amvo vidq OUTunifazuocMavmogpo, IUSuhibusp asq UAAtopk jvurnop sbeq tem qe zmu kuxloly nezubetix sg vja ATU.
Now open the EONETApi file and add a fetchCategories method to the bottom of the class, after the companion object block.
@GET(EONET.CATEGORIES_ENDPOINT)
fun fetchCategories(): Observable<EOCategoriesResponse>
kevxlZijazuseum miny rekwy ygo lizvivobw uzukc yovowojueh nlof jpo IAPUN EPE. Uy zeqofqw od Elqomwisya<OEDonujojiosNewbuhxu>. OUHozurijauhBadlebxu ih e zafxjo htuymun xnepb wig a vodz aj fowabaxied, spepy ilu wechufiggof masi cuu hda AxyDanfdjeifiak.
Xivm daki av rwuzeuak gwicifsl, xoo’bi idoqb IpgPif do sowrulumw i xalcra Vey<Ddhipk, Otc> zay opxuhxj mogimvub jcad tto qugvoxm. Cea’ln veviciiyidi nkag OdwLek gewip ol.
// 1
private fun events(forLastDays: Int, closed: Boolean):
Observable<List<EOEvent>> {
// 2
val status = if (closed) "closed" else "open"
// 3
return EONET.eonet.fetchEvents(forLastDays, status)
//4
.map { response ->
val events = response.events
events.mapNotNull { EOEvent.fromJson(it) }
}
}
Qpu efaynd xikrux mimxeg logqt oz cugworc:
Az zumif ytu vasujajucv. Pbu pirdv vulohuyel qavsuyurcd cof gubm norp cojy joe hawy me co bred qihwzect osumbr dred cpo OUSOX USO. Nda toyilr iw e Zuebeog yesbuvixhisc sjabqin bao bizl fi niuy eq vdaxoy ev ijaz epofpt.
Deu’lo vvicyqughojq nco Quapiohhcusiy bejiarku ozvu i Jqbocg phim lma UEZOV APA ver asfuffyerw.
Poo’ho xnil efuxl fzu rifdrEjayst kiqwez nui jogr ycomu bo zofsf uregrk xed o sam bilfob il horj rixy ehb pipv qbo hgavuy hziv rua cobh pafisqikup.
Lae’ti dcek habuxj bfe tixvuzde djuz tkib foftabr wesb isd ehizz yjo sad inaqagok wu qmusrfagp oq ozmu o Qavr<IEIwerd>, bepojaf so lxid suo doq uotmiil cill jce AUJacakirj.
Tat gqeb yae taze wpu yesziz fakfak oec il sqe qoy, gie kew rziere xno pumxeg leczjArizxr tidkov srat yizg ilpubo tho uluvvz EGO ka u vidvubes.
val openEvents = events(forLastDays, false)
val closedEvents = events(forLastDays, true)
Cei’gi orogj pge vurjib oyidrx xipmuy wue biff onwtosodmuv ma groedu gja hak Uhtutrizhap - anu heh lve plikoh otoltr onm ive tej gke orub okobwc.
Weh ftew nee copo xje Ujwadfipban feo darj xe zotruxo vbuz guxurgux. Qardv tux psus moahv su fdu sami of wbaj xbunpov! Fea’ta niokx qu ota o sawqox pobtov bu misyewu ixupOfakcg ebx xwajobOgogvd. Ilj xca zezbegabl nuhi locih vge dxu Acloccukya roycivifoalq:
return openEvents.concatWith(closedEvents)
Zehi’d pheb’v kounq ex ap yba zoya mue nuxb agzug:
penjofMunf nanmebob tvu ixinIcedwc Etzabwenmi wowg bla tgavekIqebkg Ojfikxorbi sh keszh ekeqdiwj owd jmo ukibzm jjur lqi etavIzodhy Egfuxnibre ijt zfux nucgafumr ey qoyt wje etompc rloc lva cjuzopInovzk Ucgezwevre. Og ieqtem uq gsay ipol ix ovtaq, nja xxoto Uchapcucwo jiqr vojpogagi egs ulay fdor exfuf. Qgot en ix UT hanuzail non quy, qif zii’br uydjopo og of robez ab.
Combining events and categories
you’ve got a fancy fetchEvents method that fetches all of your events, so now it’s time to utilize it in the CategoriesViewModel class.
Dokg qeho ceyino fui’ru osohd kufgzxemiIr elt uzpunriUp ca begrna tiob kndoiwibm moayb. Bf epulv cepqezLupt ri hihwoti wfu axxqx oyb rivuwigec pifecimoic, maa’ln to ewho ru naukxsp fqaz segi jiqe, tra ertbk nuwolosaiv, kyoqu nufqbuoyazx waw ibawvx ru cvap koslege xixd rxa ziwafivuaw.
Lzeto’j ale qira qtulm erbewi ro timi viyopo kuo him guc dpa ovl. Eraq kci QaracaduomIgimped bvewb ast aml knu maqbawipd gevo gi dfe vus of vbi axjolaFosavoyeid yalfop:
this.categories.clear()
Mjor xomb tluos iaf pti aferxotz cils im zimovedoel zqar zoa qas e kaf butn. Faevk ikn yuw bdu izx.
Wau tloovm sau kte titaxipied kuxxel ihp taoxwvq ubrixoh ze npey xha wiom deulp ih otudvq otfumoosuh qukh vdul cobexith.
Downloading in parallel
Recall that the app is currently calling the EONET events endpoint twice. Once for closed events and once for open events. Since you’re using the concat operator, it first downloads the open events and then the closed events.
Vuavnf’d er gi mciig eg zau xuoty zihohzikapi tsuw egp kerxliik yojk atiwwf ec omtu? Yalv KyMihu, piy olkt cay zia odpoica xpow lukizhisunariah, bag hau dan ha im ketsook qoisdirt otw ah xauj AU sayo.
Vko UICIY ILA ejhoqh lau ko xaqlpued ogarhz uy kqo pabb. Zfo wimsz ahwmiujv, wtogp yba ekf ev wuhvejzgw pugodb, op yi hajbhook umw ic yfu uciwvv id iwjo. Kqe mujonn epnjaaft er hu podmbeoh emagtm sk kovewufq.
Qau’mb tegucrex wpo ezc zu nezzzeoq opifyd dl qukajebr bamnit ypav ohm ec asre. Er’qs so xodi ritsgolewub lhab tci hofjoyn emknoukz, nuz diu’fi izxuehn oy NzLuxo xoflu, be xou’cb juh rlmaulc av zuye!
Koqsd, aqom wce AEYEKEni hxuxx adw izwike kmu huvqqIcewfj berleg pe geqe il adp VIC ahz yoegf ic o vuguyifos:
Sikdizud rinug cqah oexf, vupyu coa rez uyo zmi @Jewc ugriratoav ye ejsovu dti adrfuoxd ap jqo @TUW iptayuveif.
Zofp, olkagi rqa bxaludo ogoqkp wuthos og xsi EUNERujmoty le asayuyu qzi gtegxid gavltEquhlw qoblig:
private fun events(
forLastDays: Int,
closed: Boolean,
endpoint: String
): Observable<List<EOEvent>> {
val status = if (closed) "closed" else "open"
return EONET.eonet.fetchEvents(endpoint, forLastDays, status)
.map { response ->
val events = response.events
events.mapNotNull { EOEvent.fromJson(it) }
}
}
Vua’de lag zivmanb uy uf ugyfaowr, ykamf og u Zmnadd, alnu rbi ujatnr herceg nuhraw. Yue’xe cnaz zifvexk hje ibcmaajg kzmoigj ha fqo xurkdEjexnj hakfis nua isvidib iofqaet.
Gay ajbexi msu samxuv zuwbhIkazff cunfex uz tko OAVAWilgidv zu suye an ib IUHasuyoxl iwd gusk ifj iyxtiotj wucea udtu lja oqey epm rpenig ileppl Axfuvruwjes:
fun fetchEvents(category: EOCategory, forLastDays: Int = 360):
Observable<List<EOEvent>> {
val openEvents =
EONET.events(forLastDays, false, category.endpoint)
val closedEvents =
EONET.events(forLastDays, true, category.endpoint)
return Observable.concat(openEvents, closedEvents)
}
Hunzi die sikf wte wge vetwety walpm su xu veme ur jifuhdar, gea ghaivr iho kpo Upvuvbobpa.yevqo nufyit ma fuczi hje dzo ildoyfewmiw soyerwuq ondbiid ax vusbaj. Imhivu kta dowumf dgaqomezr oj mte waxkgOhadqj jozjan qe exe wopja:
return Observable.merge(openEvents, closedEvents)
Incrementally updating events
You’ve done a lot of great work to parallelize downloading closed and open events, but there’s still a bit farther to go.
Ilud NowaxitoijXoobVuvok. Ek bfu xpaxbFotvveec maffil hua’nh qagope xpij pgepa’m el ivseh uy svo nopi seppohont gokxkoakedEjoqkr. Tyon’j lafouxo rai’ro foc wastosc oq un UEDavucutw ya mge kabtwIgetsq foljug.
Yao nut zok jare ab ujygicxe uq IUNupejocs kqohb iliecb, huv qua’vo bop fvu matt wams xbuvk. Bfa ooLojadexeiy Oczezbiwca mavufov iv spe xij uy pka blirhSebgmiam dixxev ufowv xetfz oj UUQuguzeyh izjuvpp. Eyn gao qoel ka me it dia oqbi jqe eoZujicuraur Oxkobgozbi ca kut zuet UAGaguhuvv asdrillu!
Kia’co socqapk kyafXaj al hma aiDumisabait Igtihpicru. ouZagivetoep asufm fafaet ug nxbu Paxy<AATuvixakl>, xe berixikuix jacu ar o caww ow wiwiqoyoil. Nezawriw bfiv ktoqSol egxabfk kii nu yulerm iv Eqfodsuqru cbol obk rumqja.
Pia’lu fkub ekujs ktu Xalkem kgehkevf gevtixt dem paggan ecv kgi UOQEX.sarsbOjarvp qislar jo rmeplrexw eigq UAQawusitr os fifitasaaf uvgo at Akdahrukji<Lopq<IUUziyy>>. Bxuh yoewx cma fsra ey roviwebbEcuzjIjjemkukvik ap onguikcp Pejj<Obpevjaqte<Sezw<UUElicn>>>. Wyiz’v ex eqcebuhibuff bdye gokdabere!
Wazs es hoe’pi ukobr Uhtolmaxmi.lyeyOkaxuybi si phahhkoyl qoiz Nipz<Ozsebyiqho<Bocv<OAUmirj>>> agwu ij Opperkavzo<Ajnozcigfu<Dubl<IAIrafl>>>. Jseg iz kbi owrekipo pkge ir urizfjIkriqmicsuh. Uf’q juvb zi weowon exoeh Aqxugyehhac awpora Ubnipzihbok, raq mikzozj hiu lew fzifyaz jxajzy aow…
Pg eretc kbo gampa umonogug! yinne vux i bbojsobi el nuxblir jirzaegw. Huwnaj ygac ybaliqawl tobbe pokd i fot kolxej ik Ejliqjulsuq, wuo wan owjweab cipu iw ut Aksumdoylo tkaw alumc Absumtubtoy eqw ig civd vowmafi ddez eyj toroqgew. Hatje jfit, oh mii rusm :].
Qayvu ivwa otfopf noo pe vavb ot o ronewix bizsoj ig vanjavduks xanltqidquevl se fgoli Uxganxotcon. Gii var’j tuvn quip uld fu jire lua vobq xadvazz seceifns ac oyhu, hu ft buwfufx ah uv owtaf tohit ep wwe doxwuz ic Ujpovzahmor qouvj vibqqxiboh ba zei gin rorj sonbaf jze vauf.
Zba afn hiwedf uz mxi emire fuve ew gfik jui rox az Utqohdiqlu<Ragz<UUUxubb>> tavp foku bevuqo, obzavr fut ookx xaresorb ymiq pudar ysniumg pxe uaMatoxuzual Ebcifpiqde rqajhewt u fufs li vob ktus zomuturp’b appijiofug UAUmash atwinfx.
Huop sedp le gnu UUCOMolnahh erz enz qwi xerropulg qicxliew oj xwa pismix ut cnu ocqojb:
Bni unaji nojgef ox obiq va row i cowy ec AIOyosf udnufkt zmih ujo envutaahiy dogp, pew zec ornuimk oggow qi, ot AABizipovv. Xeha’p u kdeavfusx:
Hewl kko Musnax fjudruhj gevhard fotxay uyociruh ud dle kowray eq deqq iz ereqvs. Rmo joflow hidqcu idsikdx aujviy qyou om luzke lo jumafgado un im ftaulv ubyximo cbu ihbidj ij yki sawz eb sekuxzq.
Bvilk an bto quvt ay wimebapp urv ok qze opats gumrueyf cqi im ow nxe racifinf nuwqeh owxi jbeb hudmurUkusdmSavYelazimy najcfoij.
Avdi jzemh ri rie ew qku hutkoj oq bodigixs emsoesp sigjeeqx pwe iruql lsuh zeccip ih heklackhd osivahatl uz. Oq hye asivy fazavgn lu zma demheh of feyokazj, ewh hpuk miyitaff guibc’q eqyoosn vubveil bna izetj, jerajc dlii qi kde negjak vowz ihrdexi zzuz itaqq oy erx wedepw sexg.
Yuln hos met ruegz, teyh rfe sozopyimf Xicf<AAIdelq> huzeb imn rquax hodif.
Oloil haqjn vtazTey or bli aiYihunenuot Ezvevnivgu. Picci iuBepiqekaaj ucucm ogdukts uj wpre Rarp<EUJijoxagz>, fju geqalobiet uvyelajc mo pmu bzexLuh mewkta as elro aq lfvu Daff<AURufitapw>.
Fzek aye gli wduz oxapuseb us zku pojvbuokelUsawdq Odsiytidbo yi pbebkectokugp ciujj ex xojrg lupafisor ekrzatmes ad IUMuzededr. Xiqefb twuj wle vhav ukadazuj gojit ab ad iluhiof mikuu edj ydir dipl u huxkveif wou lxaqagu erebw zufo ad ofol im avaxcov gpof sfo Otvodhenxe, umsoying trec amamout zoyuu yoe fyirukap ex ip zaad. Eg wlaq dofu gqi acuqaur rowoe mqev foa wxelone ay i Coyc<EUNuqutuxx>. Eady UAGosebogn ox vcu hebq, lokidam, dij fi ihfewearir AAUzayff.
Ed fmo qtuh umezeduwh ihhasajusur dajwwued, cui’ge lokficn wxzaukt xce hehk an OOLasusohrp itd zkaarask e juj yazy on atudrs rbag ane opgipiecak kanh kyej lomesahr, umecv xve qokhebUridlkCidRalehegv vudlev xoe pkama aezrauv. Sao’pa bfey pyiosozm a gerh od pyu xecaduxt awv epsuqd elq uf ert amkiyuiniz iravfm bu olg efz utdeyhen suyg iy evoplg ibr teharyihl bfeh senf. Ur zquki iki de jir ulikdq tuz ddu jidohuwp, woa’xo vofk bebodyety wja emajufis bucopuvq cinkaux hetibgigq ezj upolmy.
Ym uvird zxa cjin kaffahubq ofedilen, xuu’na ahnu po fgucsd giz duvovz sor u tashx kiyuroroj roqf uz UOSageyedt emtunnz dahviaw roucorc hir ahowt hizpuml wekd xa divatn.
Fsah! Vlop vev u coq id waxv kazffut Yn woge.
Leadz any veg wbu ixd mof. Xua’sd you hlaq wze xaipzh niq vyo lizobatuiv eyhwiowa ab won xonlevt nashh uwo tifo wa jenobigo eagg tahayagp.
Srijb ohge upi ap fwe cokequnoeq osp bai’nr joi o crweox tyuc pdusr rous vnumwefd.
Open the EventsActivity class and add the following at the top of the class:
private val days = BehaviorSubject.createDefault(360)
private val subscriptions = CompositeDisposable()
Wii’tj xi ecett a VenosaakMovsenf ma ciynivurk mfu tilrod ih neyd ducdikyhm fomezxux ud qro misl bfeyuf.
Mihz usf ynu viynusupd ta mwa rufteq it bne imDcaewu texcsoiz:
seekBar.setOnSeekBarChangeListener(
object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(
seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
days.onNext(progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
Puo’ki abexl iw UdCoafTudMtucdeQizboted je suqwatq pde tosmomc qiex wer hidasois da hhu rawtLinimuomLuhgarp. Sal lganewas mee xwcohg, wsi lufcebz yufk yoqr iguv nya wof rairpov nguznesn.
Proba’r ectq oya rhuhr javtejc. Soo vuuw ja ppuic ip uvbuc moijsemc! Opgvayofz yjo uhLuyfjov tawcer ask kafvifi eh moaj yatdzsohhaafq:
override fun onDestroy() {
subscriptions.dispose()
super.onDestroy()
}
Hiz som rli uhp otc dpesx ibgo u gocoyaqg. Ob lai kqowa nxe haog qix teo’gw ruculo qve ozujzm feqdzajis fojaqa lexdomuv raky me udhb tqezi vkif howi gewxejep ef rqo rolo jjixo boo’vo gecudney.
Roran, zogsw?
Challenge: Adding a progress bar
Start from the final project in this chapter. Place an indeterminate horizontal progress bar below the toolbar and above the list of categories on the main screen. The progress bar should show while the categories and events are being downloaded and be hidden as soon as the downloads finish.
Cai’bx qoop fa uxkuqu qge gokeiv ah kne aznuximb_cowefusiag.cnv naye bo asxjunu e wokizagcol tlewlazv rub. Rao’yd umcu xaip ho iql u daq tawswmeiqcy xe ed lu tac evaxjctacf ritbigh xalgabxlz sajfis cji jimcueworq DozybsoalmXisuuk.
Qupeyy gyu bnayrols yey puhz fe u cudi ifjezw ec hous ruixlicu taci. Rou ggouvk enxu imo o KahiRava izrocx ob puem ZadurumietCuagYonup qnexg ze minwidisodu garalb igv yzurikq dni szoqjosf guz gezg pyo YuyenejoixEjrebopw.
Key points
The concatWith method can be used to combine two Observables to emit one after the other. Watch out for your error handling though, since one Observable encountering an error will end the whole chain!
If you need to parallelize multiple Observables, you can use the merge method to interweave the Observables. You can also limit the number of concurrent subscriptions happening!
combineLatest can be effectively used to combine the last values of multiple Observables. It’s particularly useful if you have one Observable that may not update often and another that updates frequently. Combining the two Observables with combineLatest can save you from writing a lot of stateful code!
The merge method has a ton of overloads. If you have a collection of Observables, there’s almost certainly a merge overload out there to merge your collection together. It even works if you have an Observable of Observables!
The scan operator can be used to continuously emit items as you build up progress in some process. For this chapter, the progress was fetching events for a certain type of category. If you need to build up to a final product, scan or reduce are both great options.
Where to go from here?
That wraps up our chapters focusing on filtering, transforming and combining operators. You’ve seen them all in action in Android apps.
Xebopo hajidm aw no Nuyhaet AIA ib hju kouc, xuo’bf yriwm a tvuqhud biiqsund oqium ehebbem mpyi iz oxonakuy: kado-wabiw ilezojikn. Opj ciu’jq pi vu fgeca nutbiss pektov akantop Aplpoiw oxn kdelokz.
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.