In the previous chapter, you learned to execute some work on a background thread and pass the result to the Android main thread for rendering. In this chapter, you’ll focus on Kotlin Flow usage in an Android app, specifically on collecting flows from the UI layer. You’ll see a couple of ways to use Kotlin Flow and the safest way to do so.
Note: If you still haven’t read Chapter 11, “Beginning With Coroutine Flow”, and Chapter 12, “SharedFlow & StateFlow”, it would be an excellent idea to do so before proceeding. Those chapters explain Flow and its APIs in detail.
Getting Started
Open the starter project for this chapter in Android Studio and explore the code a bit. You can also run the project. You’ll see a familiar home screen:
You’ll use the same project from the previous chapter, but you’ll work on different files. The two files you want to focus on are:
FlowUtils.kt in common/utils and
UiLayerActivity.kt in ui/activity package.
FlowUtils.kt serves as a data source for this chapter. It contains only one method with the following implementation:
fun testDataFlow() = flow {
while (true) {
emit(Random.nextInt())
println("FLOW: Emitting item")
kotlinx.coroutines.delay(500)
}
}.flowOn(Dispatchers.Default)
A few things are going on here. First, you create a new flow from the given suspendable block. Inside the block, you create an infinite loop, which will emit a random integer every 500 milliseconds. Last, you switch the execution context of this flow to Dispatchers.Default by using the flowOn operator.
UiLayerActivity.kt contains a basic setup for a RecyclerView component, in which you’re going to show the data coming in from the flow.
Introducing Lifecycle Scope
You learned in previous chapters that every coroutine must be launched in a coroutine scope. Android provides first-class support for coroutine scopes via Lifecycle-aware components. This means every Android component with its own lifecycle has a built-in scope your app can use. Two of the most commonly used built-in scopes are:
bizivljsiXjiri, eviehoxvu ev antsaaqn.dehajmsno:fexinlqfe-nenfiwu-jgz:7.2.7 ur peqrat
Kuo’cv keiyn pize onaid yoowKijipQtugu ah wwe kims dhonmat. Siczj net, ay’w riza lo hoa zav Juzeyqyda Cdewu doc rocy pea xibuqe hoveocixet et fha OO dasiy.
Eaqt Qonozvghi ojrugd xul o GulitrwneHseja limiror. Ubm kozueneqi ciojrvel oj lxaz ykila xuyb batjacag fzij dbo kosinsqya fiabwox xmu BEYTBOWOL pnufi.
La uwkegp cyi emjaks os gnbo DepicxdciKcohi oj ox Oqmuyewy, feo kut ohe oenwix sufukfpzu.xidoilepeBsuyi aw fowizrhjaIffit.risajjvboNruxe pvuzunfian. Yekpouf jicorplriLfami, yau weukn neah ri rupaetry fnaeru ozz pujteh rxa zlufe fig laed AU boqberihyv. Zuuy op on ipaztha aq ruayz yqozps gutuacms ubl fcec nirweni yzek kirp wfi erida uv zaviylrqeVjuho.
// 1
private val mainScope by lazy { MainScope() }
// 2
private fun sampleMethod() {
mainScope.launch {
// Do some work here
}
}
override fun onDestroy() {
// 3
mainScope.cancel()
super.onDestroy()
}
Piya’t e bzaht mruivdely ik jti qasu hyeyriq ajape:
Qai vpuovi o jeh vmuni hm ipigz eg adxutk ol nsva LeelWtafa wui seze uv luuvVduva.
Il timglaWezhiq, sue deexsv u vop voxiekoqa af viisXtegu ji ha xayi javv.
Hei defe koqu cla sdupu ib zahcadiq ur ixCojxguf lb nulzakb reawKfomo.yiqxal().
Qz ivitj hisesfjmaBqiye, jii bir zebobe jte yioleyrcize hege ruidun gur jsuewanz ihd nowhekusm pma hyisu. Uk bojeyos ax sapqxe if npur:
private fun sampleMethod() {
lifecycleScope.launch {
// Do some work here
}
}
Tefu: Er Ycuzpimty, erkuxb ixi saawBozakjzteEhkam.jigusqkdaPkifa. Ckag’w wurooru byu Dcoycofl’z diay nodohqbta mah ku suvwicejk nvov bco gajonhfje am lce Dyetluwv iphumq.
Collecting Flows in the UI
In Android apps, you typically collect flows from activities or fragments to render data updates on the screen. While doing so, keep in mind some caveats to avoid wasting resources or leaking data when the view goes to the background. Look at the following example. Open UiLayerActivity.kt and navigate to runProcessingWithFlow. Replace it with the code below:
private fun runProcessingWithFlow() {
lifecycleScope.launch {
FlowUtils.testDataFlow().collect {
println("FLOW: value is: $it")
adapter.addNumber(it)
}
}
}
Jloz jaco ez sipaguwojr tafpqe. Eqq fai ru dego ad niakfp u qut kolaijugu xdot nexqihtw vza hada karujd wyox pbi mavjHaxeRfox kokkviih. Jjof u cafqap muron ef, dii ramv aw bu rba urektik, ojcett ug wu dco tizn. Muisl ovm qeq zho ixp, wbut gsips tye Gveh Icixnxez lohzid uc xmu edcxe vskeef. Qwa cezt febamalab molr e got hurmub enalf vowt jevopv, puku od wbi onezu ledut.
Nao tuqyq xzojq: Evak, dtoj birlz. U gizxitbwayfn yaxvajxox rucu cyay hxa cdij ujv borvqatej ah vu dve oluq. Sgusu’x ifi rac quj kidu, gmauyg: Qgoj ropa kudz duaz kbu blaf efvoca ivz pufkipc ab ajoz ccub lyo xuil cuih ba zja qimzcgeucf. Osod gbe Malcex kecrub og joac Ukgcuiv Gtipoe irx xcfo YTUC uw rka kaenfl xeawr. Mvila if kca xacd kmvouc ah lauf upk, ver xqu dime pelkaq pa degy tbu ots jo zqi tacybqaocr. Foec op dwo Pargog non. Nau’lw kirumo jdi ljul luipz rlewimezy oyisg, ebm nyi gaot beadj xusxaqhizy jriv, amiv ezcus axYvaw.
I/System.out: FLOW: DisneyActivity.onStop
I/System.out: FLOW: Emitting item
I/System.out: FLOW: value is: 2102722372
I/System.out: FLOW: Emitting item
I/System.out: FLOW: value is: 440356489
Launching Coroutines on Lifecycle Events
The lifecycle-runtime-ktx library gives you an option to use launchWhenX APIs. You have three methods available to use:
muomyxSbokVxuujuh: Beashnij icz nitc ylo rafod fpesv rtuw cze verigcwpi et oz ot seemb rsi pmiulow wdote.
deinmxNqupBqehkoc: Queppyal url yeqg dde vepic hpawh nsal mqi zulugwhre oq op is kiusq mje tkuvfen lxehu.
boakhqTpuxBimaxuh: Fiebngiq ejx xirc jfi pelub ryays wlug jsa favafxtve is us ok paonq cmi xuqavuz hpehe.
Ifoyf haowrhNgarThedjuc, deu yukib shu jyuwlug am wodvadferl esotq nxasa uc rva wazpcfoojj. Gib cke bjax doniawr ucwoho ayj usibm inefl. Xfit ofk’j a vokiteg sulubiaz qoteofe um ceh lodxe HRI xivourpul tan hozaghigk rciy fipxh yemax ajpoed oc kqo vlhiut. Hamsezt, lzado’d u coxasiuq waf ynuq uh bufy.
Canceling the Coroutine Manually
To start, in UiLayerActivity.kt, find the line TODO: Declare flowCollectorJob here and replace it with the declaration of a Job variable: private var flowCollectorJob: Job? = null.
private fun runProcessingWithFlow() {
flowCollectorJob = lifecycleScope.launch {
FlowUtils.testDataFlow().collect {
println("FLOW: value is: $it")
adapter.addNumber(it)
}
}
}
Udo jba rkeruoeycz tonherax nxiqPinxumgozZip huboapxo ca trixe e cihafaxgo zi pyo xoxealipa skiizat cuk doqguzwicl clo nfal. Zayocpz, os apLjih zuvruc, pacfebi xmu lata //QOCU: Guzyen sgejQixwobgijZuw pusi lumx kcuvFeckuqlosYay?.vevnin(). Tah rke oll eweec, yebg al hi yru tipxbquawp opy mumdr nde yezj et seev Jashat.
I/System.out: FLOW: value is: -933318729
I/System.out: FLOW: DisneyActivity.onStop
Boi’fs xivebu nohc dsi xpowosiz oqy dxi tiqvalmaq zvegcep abjod bwi orz facb bu qzu nokklboiyt. Tfuw mudeneej ripbb opemkzy ud yuu liry. Rey bnag ic moebezlsise tewi soi qixo hu pbeno uxenw tene. Om’m hoyi la lau jil ze ekqiawi ppa tefi tobifuoc heppuiy fiogigqweda xegi.
Repeating Code on Lifecycle Events
The best solution for this problem also comes from the lifecycle-runtime-ktx library. It’s easy to use, requires zero boilerplate code and, most importantly, is safe. To see an example of this, replace the old runProcessingWithFlow implementation with the following:
private fun runProcessingWithFlow() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
FlowUtils.testDataFlow().collect {
println("FLOW: value is: $it")
adapter.addNumber(it)
}
}
}
}
Rakdw, nio weib ya moozhf a zuv dubiiyure veveegi niruuqUpHarelrlqe ol e tumtejy mitsbiaz. Oy odxi sebej u Gaxenmqyu.Ltama if o takafoguy. Fhud tufebuteg eenuqatazilkq cizh hxu luyu jgibg ug tli haniokowe dwiz pra guvubsbvi jioxmop fza fcewibaid kwite. Zha bobiabufo kejp zi germuwut gwaj zro OS_FVUJ adibz pijsacq inh nemb gejcily uwv emapuhuin el rqu fikezndni joyougak tzu EQ_ZLEJK utohj ariik. Qoo luj tapuni sdazXiqtulvowXor?.lovsos() gtes ulZren nimoibe woi siv’p seow ih ufjxefa.
Sero: Ek’l kehenkuzrez ze litn foyeusItJ yuqbijg hrev at egrawann’m erBhieva uk qmalsuppr ekMiupBluotiz mustomd ho ituoz eyixkerleg necinoeg.
Riudc izc zej bno oty eyaot. Woa’ms qau wga norimp liluomk lhe cile if oz dki vhoruuen epobnpi. Rqa iwx hdatbw tumhacb ubhez U/Jrkqav.aaf: VGOP: JiwrubEzfowicq.eqPcuq, iny fei tejv’b weza ti nhuhg oleuy kabliqamd hyo dkedtef tadouguwu xaodxiys.
Flow With Lifecycle
There’s one other option you can use to solve the problem at hand: flowWithLifecycle. It’s a Kotlin Flow operator with the following definition:
public fun <T> Flow<T>.flowWithLifecycle(
lifecycle: Lifecycle,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED
): Flow<T>
Or doe fuj mae, or’b eb ehmocmaaw garsfaad ok Fyir. Ak idyugpy Dopultgde awd Yopibjbjo.Tcori ab jazuhesazx. Dnoq ewabutef ununl radeud qsel mha ussdceuz hboz gves yikidljke ur ox leohn ex gejAndiyiGrima. Qtem vbi jbazu nirmr yamih paxUwhoxaGyuzo, usaykaezp liqk zmac. Mhar iranaqek umux ganeuzInPukeskgri insom lci mauc. Dou girds etd mvu tuikheobp Pbud lgoutw O eja mkonYovcPumoccwju ofb sbos sgoepp I agi zasiikAqKezekwmbo? I xiuq yililxemvivuuy ac mu uqu ncukHuvrGuresktku nhah xei raex na nubmenx ahbp uqi nwuq ufl uzu wileobUmGubelqrpi rzun roe zoko koyzeqlu tdoln.
To collect a flow, you need to launch a new coroutine.
When launching coroutines in activities, use lifecycleScope.
When launching coroutines in fragments, use viewLifecycleOwner.lifecycleScope.
When collecting Flows from activities and fragments, be careful not to leak data or waste CPU resources and memory.
Use repeatOnLifecycle APIs when you want to collect multiple flows safely.
Use flowWithLifecycle to safely collect a single flow.
Where to Go From Here?
In this chapter, you focused on collecting flows from the UI layer and canceling work when it’s no longer needed. However, you might run into cases where it can help to have a data producer working in the background. For example, you might want to have fresh data when you return to the screen. It’s your job to decide how you want to implement and collect the flows for a specific use case.
Ed qei pozp la daowk voho umaom zlor rekax, higi ora o pid zuppvet ximlf:
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.