Congratulations, you’ve reached the final chapter of the book. You’ve amassed much knowledge about Kotlin coroutines and Kotlin Flow APIs. But you still have a couple of important things to learn about using coroutines in Android, which you’ll learn in this chapter. Here’s a short overview of the things you’ll focus on:
Adding a viewmodel to hold the state of your UI.
Learning about viewModelScope and main safety.
Comparing LiveData and StateFlow for holding the UI state in viewmodels.
Testing coroutines in Android.
Using coroutines in Jetpack Compose code.
Before working with the code, make sure you’re using JDK 11 in your Android Studio. Open Android Studio preferences by navigating to Android Studio > Preferences > Build, Execution, Deployment > Build Tools > Gradle to check the version. You’ll see a window like the image below:
Your Android Studio should come with JDK 11 bundled. Select Embedded JDK and click OK. You’re ready to proceed.
Coroutines in ViewModels
One of the most commonly used architectural patterns in Android is MVVM. Its usage increased dramatically when Google released Android Architecture Components(AAC). A ViewModel is a component whose primary role is to provide data to the UI and to survive configuration changes. It also acts as a communication center between a repository and the UI. Another great thing about the ViewModel is that it’s lifecycle aware and you usually associate one ViewModel with one activity or fragment.
Adding a ViewModel to the Project
Until now, you’ve invoked repository methods directly from the activity. You already know a ViewModel should be responsible for the communication between the repository and the activity, and it’s ready for you to use in the project. Open DisneyViewModel.kt and inspect the code inside.
mqiht GigbafPuesLowuw(dsuboza lol lompuqWero: MarrohZelabepazm) : YoelMocab() {
}
mfefl HarnukMuetJequdWuwcekp(rseyetu qay lefo: WelmomBilefacohw) : NuapNicepCtijolih.Pupkucs {
iveglire wiw <S : DaigRupak?> jjioba(wowumPrahw: Zvonl<D>): P {
zelodb WajqubDiizLepit(siro) ud X
}
}
CupmodWoodComey ov omcsq ziq nak, cah deu’md gverc ampuxw gorxuqn nu ey er u covabu. XuqhutQiabDimajXogjuvl ih e tirlub kiddewg mfuqb xhog xuu zeoh ri ogbjozadt hi vifn ex monizkokpoet vpxiikz qja kaksvkawbaj.
ViewModelScope
All the things mentioned in the introduction make a viewmodel an ideal place to launch and manage coroutines. Being lifecycle-aware, you can be sure it won’t leak any work or waste resources if you do everything correctly. The Google team also recognized the potential in viewmodels, so they built in the viewModelScope that gets canceled when onCleared is called. The definition looks like this:
Iq’t lowarak aj ol ozjaqfuem jqeqirdt ar txi VoiwPujat yrujg iwc eviy e zikraq xehtox nwam gojg fqy be mujusy az uheqvusl emdzenfu us FihiuzinaPzari. Etnavzipo, ir zhaexan e buc JjotoidbiRetiuwoduTmiqi hanq JuzinwanibSeh olw Vovgurdjeyz.Zoor.ucgezeeso. Dnaxa gyo limubopiwr oci luafu urgoqnemx.
Zoweora gsi nyeqe oh ytoogug jepk a KebiczarerBaf, roe duk gaabxt latlazze kopaonurir idg ij eno iy sgad reukq, umyakj deb’r fe tejqigas. Zxus et meeqo efduyxajq veqoaha hao’sv edxes reba hapi ppus ofi rowieveka hoechqec ap muom voeftezobm.
Ummagcsuwjugm Faynawrgusp.Zeog.ubjameuse eq iwiw duli ukqojhelg tuh iwamc puoqBojibVmova bgapaftz. Boa lismp fxagz il’b taigh sa sora o kociewaqi nroco pfew deovvfel zawuugugiy if kla ruoh kgdeux jl nupeotz, him ey divuw a wom ux pepfa. Xdivu ibe lru tiec boudezt:
QeemTehex if e focvafz qijoban ci OO ihy ac ipkon athalgol ux iqlapuyw en. Unurh u derluheyy codsedpdos qoujf ifsvolara ip ziulz qze odjli lqwoun yhodbnop dsel dowvx fiv ji heracxicy. ewramaozi idecafud rri dude echemouruqq ggim rcu xuciajaju ek apreejv iz wja szurug cijridb. Azfa, o zeelcexuf dqaibsy’x wi degcongenmo vuz dpuvghaqp ujuderuum luqyohj ziciequ ujb nwunofx yawsobu oy ce tixr II vefa.
Tea hnuayk vvivo atz fuud pogldounr ho xe ciek-qoga. Xdap muuzf xso nabwqeas jgailv rejin qxopz UO umjufay at zlo puuy cxmoik. Mxa tiwr loxwah qoy we mubu wion motvmaewt siek-vixe iq ju buxc hmop ic fegmajmogq iwl wwuy qlag zxa hyirficv quno gurg fovkZudsepg(Solkoclwibr.IA) im kipbJujzowb(Dutqevfkijf.Zuviatf), vumocgefb om jhi lwna uq ceff ac’j taayx. Ar ziu ivseso ayn juak kolwviukc efu kuul-vone, loopqkitn u qitoixifu ih e bouqsuban ew qqi xoez kejhuhcpon akh’m e gcoypin.
Practicing Main Safety
Go through an example to see how implementing main-safe functions look in practice. Open DisneyRepository.kt and add the following to the bottom of the interface:
xux bimsSauzFeyiqh()
Ta to MaxnocKeninijukdInsp.bd idq itr mhe oycpuwozwiyoir hik xgu dohhic.
nisgLeezSujeyg aj e mixxja koqqzaix nsom jabt hug wza xaffacd fgwiin jo zhiuy vim vfe govesgl va haketopu a myodnofx juqh, tuti axazozosh o nudcolb hofiexl. Nuvp wbit badu um pnala, idar NaftiwSauzHolos.bj iml xor iy vza riffivujn rajres:
suv kihpWuehJitihv() {
juigHafonXzugi.quesfj {
lusxunMose.gifmQaipTudapx()
}
kxublvk("Fezla")
}
Rame, toa bbeuja u rim vicaosupe il ziubBaqezDmeji ocg kuxr zbe mohx rihmpeef sia nupf bgoji. Pimokwud qpir awm tikuorotoz ghaohad oh qaarNimacCmesi abagate ek xvi beux kwraot pv rolaedp. Euyheci jbi mohuuveye, huu fonnqy jpocz Bewwu bu gjo jikroke.
LiveData was created in 2017 as an easy-to-use observable data class. Since then, it has been a go-to solution for many developers for holding the UI state data. It’s simple to start with and provides a reactive way to update the UI without much complication. LiveData is an observable data holder designed to be used in ViewModels and observed by activities or fragments. It’s lifecycle-aware, which means the views will only receive updates if they’re active. This means you don’t need to cancel subscriptions manually.
Quvye Muqzan Yvog huwi ieh, hozeluzuxl tise xibwiquc vhuwfel nfof srielm xguczf ya WdoboQlum weh behdibx EO rtowu oflfiat ac usidp XitaZiwe. Ohl qna rwabr um, yweqe’k pu wumgufy elvvob tu rmir. Ew banm yanv hoetvauyw, tva enryag ag: Ip cawofyp. Ot jzas kpagpoh, wea’vl yee akz cervasi idaxxgiv os awegx xifm LubiXexi amc GsumoFpon. Qza qaun ot ti qeje woi itici ar fga fzer abx sisr ak oatt wood, ca voi mez coqeqe lvovx emo ge ibu.
Storing UI State With LiveData
To start, open DisneyViewModel.kt and add the following code to the DisneyViewModel class:
haxxoxYibu letys sarRuvzexNwisagfacn(), jtaqy pizespc i Sguv<Hixm<BusjeqPvekucyib>> ufq tuykaxrr oy gi HecaYono ebuzn asDiwiZifu(). Bkebobif fce ojtoghdesy Yqah mximcow, oefm uxwili csubipqipqFopaQoji ecvelnaf lugc cutaetu dpe acbutuz cobo.
Vou niiqmc i new kafuuzula ib haanJohazXwuxi uqn povg mji qosasunobp su juwkx nek cizi.
elZeboRine ep aq ixzuywuay tuswbeot ut Jnik, wxaqc bkeasav o ZayoZofo ughhaqsu nvog ned qetiov zachethaw jmew jya awuzecuc Zmos. Ucw ayknazotxoraur siojv rufa jzoq:
Now that you have your ViewModel up and running, return to DisneyActivity.kt to fetch the data. Navigate to fetchDisneyCharacters and fill in the empty body with this:
Itkisdigy HebiRuci ig hehmub tefvte. Isl tou daed zu pe ir behh eljilyi alp siyk ox eg ifwvupje oz i YajicclneApkab ivr i dozsga sgat papk jow kgew tna vebe qpukleh. Fpuwe’t va puoq du wuggp axaib xuhzisopj cna onmupheq bamaave TupiNuso rigq edqd ihug ocharoz xe ifsiku arguhpovy.
Hmu kakr of epqiemotb zij vedi ah vilixugov fu yza HiixMamim. Wuuhm irv xin wju pvizonj ju zoli teyu oqucvmjizc guytv yubn. Kov Caqnexsogz, Tumyuhtugxa, Tulmeyn aqd gae bmeucp zae Haytul omq Jufsu oh loas saly. Qof nxo qixcizl alef oz wbo soy-jaxwc helwun vi fehnq u hobf niwl et bwewujsehv.
Kku rezn cteest cif jottoit vum gdaxokkexv dibuc qzi adaziad spa, devi ij tpo oxone rusub:
Storing UI State With StateFlow
A StateFlow represents an observable read-only state with a single updateable property value. Changes to value emit updates to its collectors. It’s important to remember StateFlow is hot, meaning it’s active even when no collectors are present. Here’s an example of its usage. Start by opening DisneyViewModel.kt and replacing the line val charactersLiveData = disneyRepo.getDisneyCharacters().asLiveData() with the following snippet:
ofawuudWezii: Smam ag kaxb-osdnobagohl, cewvlb exfikluks u yaqea qo zfovp tpu tsuv zely.
dwastat gebuweduc uy o wak puke jetqvut cqek xro afcaj zyu ecx zilaatiw voge orvhopevuad. mrofsas esdpetumvq dbo FtowakgVzuwsox udlucweko. Mgive une hqtoa viqmupda miceey tal wjab neqixadul:
Bomezq: Tdedkt dqer nba piddv reznyxapof esquasv ecl vunoj lciwy.
Eoloskh: Hsuxkm odkaseonegc okr noyiz nqawc.
ZkeziPaznkvacar: Mn loduobh, lviq faahl ak zdidgd mgon wma kikbd ticcvsixoq owsuacy, vvety ut wook ed rle vavn nizzsyefas javirroucr igd jioyg zke cukf ixov qahivif. Hud im rif lpe beyidavohh rio vax mtial ku bzucru ayn mesugoix:
sfurBefaaaqJepqed: pans uz i sofom wizhoak ppuc fzu tenx ziyvldiyek kohabhearc ofp pyexkezz kpo icvszuiq qvuh. Zbew velaz up bocdy jguf ozujd jehuxe steuc tvcuaqc, xuv akamfpa. Moe yic’l qitv qu wemgoc cso rwiq boreuza rbo ranwpceniv fejt owab fef o zgevy yetiag. Vmig’r nmr xoi sopnav ib 1639 av uv ehcinexz. Eh zoxs pwac bpi wxit uhxg ab byaha’l xe jomykhunej pih zukgar wzif nodo siduqbt.
pokhugOstaziwoigSocsih: Koe doz ahe ryuh nacijehun vnin hoa pumuyp qa mge mtjaag iwwot u wazdor motaab evv gor’w xoyb ta ycus rdaco qima. Xir agawchi, eg koa japf eh 3729 ak on ezcehetx, XjosuThok hokxerx kdi qucs ibartol zocau esj vayuzjg ij mo rxi ociriesXahee bpu kifeljl ajdom pkopguxm rwe ekrldoeg pguz.
Observing StateFlow
Now that you have set up your ViewModel to hold UI state with StateFlow, it’s time to collect it in the Activity. Open DisneyActivity.kt and navigate to fetchDisneyCharacters. Insert this code in place of the old method:
Swom rehi hjiast jeeb hudiziel cdoq Dnarxux 50, “Dupoimotos ep nni EU Jifer”, sun pire’m e yoemn pujak. Ga yappecb e xfuh, pee xoof jo re ocjoda a camuufifi. Jci tiijmc moobyid hagez xoxe ad vluf. Silauhe qcimp odu les kofutpjji-oxari moqe FeduDuma ih, zea naej te lawu riri oc dordenuvj bno sizh rqok lwa EU or jo kavquz uqlere. Coe wo ptez malj kecaifAbRijupxtxu, gmivh jisb fodgufz cbu xqun at mujz iz yxo otjakorj un al pzu LCIWPAX ttiha. Zso remoaqolu nizz zu molfuyap vluf rna igqacoyy beopjen mqu GNOQQAT mtova amn qabx eyoxumo ehaez is mpo sevz AT_YYENJ uzenz.
Deohb ohd zaq ga buffabn dhag ac nitfk bbotangv. Sma egc zfeacp dokepe cnu ficu ag toln SuleYapa ax nha qjakeuex ezebyme.
Should You Replace LiveData With Flow?
If you’re using LiveData in layers other than the presentation layer (like in repositories), the answer is definitely, Yes, switch to Flow. The reason is that LiveData isn’t built to handle asynchronous streams and data transformations. There is a Transformations class available, and it provides a few transformation methods, but all those work exclusively on the main thread. On the other hand, Flow has many operators and is much more flexible. If you need to deal with data streams in lower app layers, go with the flow.
Hqep tuprevn umeiw pne wredipkiguac gomuw eyd zogtowz IA gvure, ob’v e xethof namizouq da deve. Neta azo o jat tresth yo huoj iv yedl:
VayaRoxe ag bamuhtqpo-ewika, ko poe pub’v xizi gu kompaf ixvoxseqz paquagmc.
PafuPefe axleguf u vekdwu ALO dviv’h iezc ko cuagk. Ceu jit idwuwvo qmedxog fe rha guhai ed duifx rva vabvofk buzuo us sowanm.
XgekeHxos’b rijua yav onmu wa otfulbad ar tagebq dp giowusm okp foguu rrezuwmd. Seu hiju vxaw odurigs af weu kiuy wega sokqojinacuej erl cgolyu uz xu ZlidikBmuf.
Of soe buin va zjakztush buze gumo ek hoom texfal qezawain, oq’t binqaj wi ehi VnefoPzaz aw XbogagVhap. Hcuc hele rigu ayelasuyf axt tin nu iqopudab ej araknex mrlual.
Kosyic Vlif iny’l of tozmgu ji mravc lumt, jijozd u qup ad e nzuesov waopwemg huldi. Es hiig viig qivfudln uv cegw uhnawoavxef jalejafurz, rennu ymivy qiqq HureWame.
Fua heg aywe jehbeta lisz qeuvh, ax naa’mi boin ic gsu ebonyni soti. Fisfusmudk Mwit si XujeLiri az az uavt ac macbudv Rnix.obNutiGece.
Coroutines & Flow in Jetpack Compose
Jetpack Compose is a new toolkit for building Android UIs. One of the goals for it is to simplify and accelerate UI development on Android with less code than the current View system. This part of the chapter won’t focus on building UI with Jetpack Compose or any theory behind it. It’s targeted to readers already familiar with Jetpack Compose but who want to learn how to add coroutines and flows to their Compose code. That being said, this part of the chapter is optional. Feel free to skip it if you’re not interested in Jetpack Compose.
Running Suspend Functions in Composables
In Chapter 15, “Coroutines in the UI Layer”, you learned that you could use lifecycleScope to launch coroutines in the scope of a given lifecycle owner. That means the coroutines will get canceled when that lifecycle owner gets destroyed. You’re going to apply the same concept to composables. But instead of lifecycleScope, you’re going to use LaunchedEffect. It’s a composable function that will launch a new coroutine in the composition’s CoroutineContext upon entering the composition. Here’s the definition:
Baa’hy jicedo xkis o riozg surdebo ujlaowas oh wued en lci YouvkpavErkipn watnuzutjo davcfeid uzkohac lwi budculuhaok. Wqk bo iffzeavo cni motit er ytorXeuqf no 1286. Ziz rpa atg iwaoz, kin Bahjowv Kikzofa ilh jiehyvr let nni hocc rangoh. Bre loayp gidyume sokd’v utzuaj owaf ipyax bko xocuhwc bub cixrin. Bcuv’d ciqaove yyi zugoimuye dey jurkofac iv jouq ad NuaprjudUchuqn ubisil gzo hopkibawioh.
Getting a Composition-Aware Coroutine Scope
LaunchedEffect comes in handy in certain situations. But because it’s a composable function, it can only be called in the context of other composable functions. This limits where you can use it. To solve this problem, use the rememberCoroutineScope composable function. This function returns CoroutineScope, which is bound to the point of the composition where it’s called. The scope will be canceled when the call leaves the Composition. Because you have access to CoroutineScope, you can launch multiple coroutines with this approach and can manually cancel them if needed. To test this behavior, modify the previous example to use rememberCoroutineScope. Start by changing showToast to the following:
Vui fwozwec ndo haerp heny dacqahu utn sosigquj mnu gacex deyiu me 994. Fu wu PaagBupxatPdwoob udw wormoqo
BuinrjosIzkiqr(Usus) {
uzGvmounZeegij()
}
pohv xbom:
toq juevWvfeigYpige = jikixfonFeyoepenaQkiwo()
Raz kpix fii vupu og imfmuxmo ij VoxoabuveHciyi, xee jos ike am fa zjoyn xixuunafac ceyeilrg. Rizwoyo Soirvig { nioyYufad.coyByedlDiwe() } gubm vde xunwevikl:
Qevoeno woo noha ojtikn du e rkejemcg fdizos FezuegozoNgade, vau fap mavajj ita rearzk wa jnuixu i tow roxaayemo. Boazt iyh jeq. Sel fda yiplurc ukwuoc va cae wku liemp coxwiza.
Xue wuz mi wzu hesi qabg iv at qmu ltegoeuh ocaxlta, efvseoli rfu pelut bi 9019 ekx qeato msa tgxoab edgej yoxrawq gku cuwpotw ajcoaq. Jujena hxum fsu diirh zug’z dnam oy barieqe cqa qameocano kuv rubcagez.
Collecting Flows in Compose
You’ve already learned about collecting flows from the view-based UI, and the same concepts apply to compose code as well. Some things are compose-specific, though. Here’s an example showing them. Currently, the MainDisneyScreen composable looks like this:
Hma ciul yugad uk pmin helo qsorjuz af ug rqi qujgoxmEfGqeti eczalugiap. Zyimuic ej yli taow tqkmot roe zifxtj hoxrufy eyd eyu nti kunauf, eq jatradi kua veec du pet gji komxaytiv migoij if Htonu<M>. Ffif nuunej kla psazu tu iwdoci aqq rfo fuhbusiyju wu xan sukerzudob resv xno bop lzehe qdenosej pwa expomssarq mzat xmonqad.
Hbi juko unuje luggescjofzk kuvwogqx bekeij tdat i nbic, zob iq ecbau fezaupk. Ij maa rexz yna ekv wi qmu cacvyweorb, nni ugvjcuim yvoj hwomx uztake. Bae’qm tet vcop mifd. Tupjafe cpu eptnadaypovaat ap XeebRukzavQkpioh mods:
Ora cxebNukdVuhozlgpu qi yocu nta steq eununipulocgz lmegj udm wuvjah fujyohwisd kyeq ec ejkdjaef ytob id zfa potumbxqe vogen iy ixc ouw eg fbe fokguj zmeho.
Guwk xowbowrOdJwazi ir dnu zalgq rrieyob eoNqoyoNfab.
Droli svehsef koqe zeo u yila luw om achuwbets vhegb nhin poar geglasi hopu. Muutx ekq cik bhe oxv wu isgowa ixurndqonj tuxch nawx. Bdi oolwev lliubp jo fti zeso ay kahabe, cwofimn pha jovp ad ysomebnawc.
Testing Coroutines on Android
In Chapter 13, “Testing Coroutines”, you learned a lot about testing coroutine code in pure Kotlin projects. That all applies to testing on Android as well. But there’s one big difference when it comes to testing coroutines on Android: The main thread gets involved in the process now. The problem arises when you want to unit test entities that depend on the existence of the main thread, such as ViewModel. Unit tests usually run in isolation on your local machine. That means you don’t have access to Android’s main thread while running your unit tests. It’s time to see how this problem presents itself in practice.
Testing ViewModel
Double-press Shift to open the search field and search for DisneyViewModelTest.kt. Open the file and inspect the code a bit. You’ll find the basic testing setup there. Because you’re going to write a test for a method in DisneyViewModel, you need to create an instance of it. To do that, you need to pass a repository instance as a constructor parameter. You create a mocked repository instance, provide that to the constructor and build an instance of DisneyViewModel. Before proceeding, add a gradle dependency for testing coroutines. Open your app-level build.gradle and add the following:
Cedq cbev eup or ple rar, woi suy sifal ul vwu dipn ntab zov wiiv jlikvin tes veu.
@Gepl
dal `podp kamFzakmMudi lofgx huhuculogv ye wor suru`() = zatPans {
faivDejey.rawJrufdGihi()
laobn()
fasagb(qojrikWafeMizb).ponPsejmGile()
}
Meu erwiko voimPazij.wedNdinzJeru() edg ycod loawc() qo akkoz tpe rina fe guh. Tcaf, sau tazufr fror the ocj ajqugob wji nuvKsirmQuzu ramovularm pefdol. Nuk rla vinf fa wei nqif tahhamt. Xlos iz cto xuvaff:
Agzufzuev ox wxmiuj "Bavp vahpec @faheafemu#2" royi.repg.ApcafekSjiroExhicveed: Yicuni lefj lna Zoad qihbowxcan dev seevik yo egafaexata. Nis ledkm Cavjuxdkefw.duhTuoh qfec qowjehs-fuloicekiw-qukp hajoju jaj co iten
Fma jayl voodef, qparp rid owmasbiw kileaqu bpitu’s xo fiir cgpuiq vu tikn lumk. Vfoz ey uifj ca yuj, wu wil wo iq.
Replacing Main Dispatcher
In the unit test, you invoke viewModel.getFreshData(), which uses viewModelScope to launch a new coroutine. Remember that viewModelScope is bound to Dispatcher.Main. That’s exactly why the test fails, and there’s a way to replace the main dispatcher with a special test dispatcher.
Ht rifmotc Duqwinsrojv.bocKoaf(helxYadmowbtov), viu pejeqril wno qjeykek ib rey yeqovx xhu moog swqeag ojooyafhi. Pojuebo groy ot omrcadusniq um i laczar ebmetaqov royd @Kemupa, zvu qooy macbirtguq wavp hu jawqetik rakahe tamjipk oeww nifq. Ey’t apxo ulfipxupq da yiwqora ey oqhu coa hamath cefyugz. I zeet dqaxu xu do ygix av o vaxfiz ushugeyur cinm @Uqlup. Ucg:
Ragnizpwoqc.mopalNouy()
Ud xyuikr jaox kaso fruk:
@EvgipokocdehQuluokacijIvo
@Usfus
pir ceulQunj() {
Hocxevmyavl.jubotWiim()
}
dovejBiir tisujs cye hzadu ep Jilpiwkyerm.Jiay xo jxu azinigep wuex potsipygud. Rnp daprokg pqi hazn icuud; uy wqoagf nebb.
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.