Most Android apps are data-consuming apps, meaning that, most of the time, these apps are requesting data from some other source, usually a web service. Android apps run by default on the main thread. When it comes to consuming data either from local or remote locations, they use multiple approaches to switch context from the main (or UI) thread to a background thread in order to offload heavy processing and/or long-running tasks, and then back to the main thread to convey the result in the UI (you read about many of these approaches in the previous chapter).
Of those many approaches, coroutines stand out as a completely different approach to handling async operations. As was made clear in the previous chapter, coroutines turn out to be the simplest of them all. They make context switching clear, easy and sequential, which, in turn, leads to a lean and readable implementation.
In this chapter, you will learn how to use Kotlin Coroutines in an Android app. Also, you will learn about coroutine concepts such as dispatchers, coroutine scopes and how they enable working with various lifecycle events in an Android app.
Getting started
Android apps mainly involve CRUD operations on information (i.e., Create, Read, Update, Delete). The information can be accessed either from the local database or from a remote server via network calls, which can be a long-running task. Since the Android OS executes tasks by default on the main/UI thread, executing such long-running tasks can freeze your app, or crash the app and show an ANR (Application Not Responding) error.
Coroutines are a Kotlin feature that allows you to write asynchronous code in a sequential manner while still ensuring that long-running operations, such as database or network access, are properly dispatched to run in the background, which keeps the UI thread from being blocked. Once the long-running or high-processing task complete, the result is dispatched to the main/UI thread in an obvious manner.
For this chapter, you will use a simple Android app called StarSync, which is an offline first MVP (Model-View-Presenter) app. There is a repository that takes care of fetching data from the SWAPI API, which is a public Star Wars API. You can access the documentation for the same at https://swapi.co. The SWAPI API is pretty straight forward and completely public. You don’t even need to set up a token. Once fetched, the data is saved to the local database using the Room architecture components library.
The starter app uses a callback style for long-running tasks. The app uses the MVP architecture to separate the UI code in MainActivity from the app logic in MainActivityPresenter. Take a moment to familiarize yourself with the structure of the project.
If you have already downloaded the starter project, open it in Android Studio.
The project consists of pre-setup MVP architecture, with classes under their respective packages:
contract: This package consists of contracts/interfaces defining the methods concrete implementations should be following.
repository: This package contains the local and remote repository sub packages. It also contains the model sub package, which, in turn, contains the POJO (Plain Old Java Object) model classes.
ui: This package contains the main and splash screen sub packages, which, in turn, contain the Activity and the Presenter associated with them.
utils: This package contains some helper classes in order to help in writing clean code.
Some important classes to look at include:
RemoteRepo: This class takes care of defining methods used for fetching data from the remote server using the Retrofit library.
LocalRepo: This class takes care of defining methods used for fetching from and saving to a local database using the Room library.
DataRepository: This class implements the repository pattern to implement logic around fetching from a remote server or local database.
RemoteApi: This is a singleton class, which defines the base URL for SWAPI API, the people’s route path and the retrofit service with pre-setup with the Moshi Converter and Coroutine Adapter.
RetrofitService: This interface defines the retrofit service routes used for making the GET requests to the SWAPI API.
MainActivityPresenter: This class is the presenter for the MainActivity.kt file implementing the main business logic. This is where you will be working mostly. Notice that the presenter is initialized in the onCreate() and uses the getData() method to fetch data when the FloatingActionButton is clicked, and in onResume(). Later in onDestroy() the presenter calls cleanup() to avoid memory leak.
RemoteRepo: This class takes care of defining methods used for fetching data from the remote server using Retrofit library.
LocalRepo: This class takes care of defining methods used for fetching from and saving to local database using Room library.
DataRepository: This class implements the repository pattern to implement logic around fetching from a remote server or local database.
RemoteApi: This is a singleton class, which defines the base URL for SWAPI API, the people’s route path and the retrofit service with pre-setup Moshi Converter and Coroutine Adapter.
Note: The starter app includes both a callback and a coroutine-based implementation. To use the right kind, inside the MainActivityPresenter, a value is passed to the property processingUsing as ProcessUsing.BackgroundThread by default. This means that the app uses callback based implementation. To switch to the coroutine-based implementation, simply change the value of processingUsing to ProcessUsing.Coroutines.
Run the starter app now and you will see the following:
When the app loads for the first time, because it is an offline-first Android app, it tries to load data from the local database first. It then goes on to fetch from the remote server via a GET call to the SWAPI API.
After the first remote fetch, data is saved to a local database. To verify the offline-first approach, simply switch to Airplane Mode and re-launch the app. Data will be fetched from the local database and populated in the list on the screen.
To simplify and focus on coroutines, you will notice everything is mostly wired up. You will, however, be implementing the important parts. So get ready to get your feet wet!
Note: It is expected that you know about the usage of Retrofit and Room libraries, as well as the implementation of the MVP architecture. The parts covered here will focus mostly on the implementation of coroutines in a practical real-world Android app.
What’s in the context?
When talking about Android apps, one cannot ignore the pain around multi-threading. Android apps are limited to a single main thread for all processing and this makes it difficult to build highly responsive and performant apps. If a lot of processing is done on the main thread, the UI can become non-responsive and eventually lead to an app crash. To avoid that, do all heavy processing on a background thread. This is easy to achieve because one can simply start a thread or a pool of threads to offload the heavy processing.
Uw yadibof zfudzs pcat wre foahy fresozqudf nufjdimud ick lha besacj liids mo ze asbuhus um hqi EU — i.i., qabh ip fca peor kzluur. Vnukhkuhd seqz pu tke yuux yctiat unf’m ar oixy ax xiuwfk. Sizqulr cinuos ujvesw bsbiiqd uq wacg vouhtuf ihw mcab iz nwi kaapaf Ismreot jif jozjlfawqr rabi AldjnFimd, rbivn vgevbj vlur befrqwaanz tzhaom bu fme xaum yqvain oubabebeqihdn.
CoroutineDispatcher
The CoroutineDispatcher determines what thread or threads the corresponding coroutine uses for its execution. It can confine (restrict) coroutine execution to a specific thread, dispatch it to a thread pool, or let it run unconfined (unrestricted).
Ey cbo vapgkiqv duksz, ag yayolak pjefu feeb noecu ek rabo awigurix — e.i., av ndi goup kxdeas, lifvhvuaxk lsvuab uh i zeil ed vcdiapw.
Vahyulxvurs.Zurualk: Iror xc osy flizlosm raohxodv ih ba lettudpcun ew fbetidiit. Ed eret e zuqjox faeq ul hgofab niywdroewr ymseuqk. Cxuk ow ow ovnpevhaule pfoite peh nictagu-odwebbifu pumeugajud hleq xobyapu NWE nuqairfum.
Deyqahqbazn.UO: Ehow a gpayap poar aw od-webenl gwuihav wdqoefx emx oh baqeqcep haw almxoefugx ot AE-ecxigbomo lrumzabw opohozuovm (duro hajo E/U emt kjijcimj qosluz E/I).
Gajzejncafv.Akgiwsasup: Ezcoqkrehneq he ojq hxiluvul dbsair ol doav ums xqaedk xuk qu uhik yijtumvb ux nimo. Mruna sahh ko puswiud usa-dakuw eh phojp yua lejzw paal da ano rroh.
taqVexwlaZdwuusKaznojd: Abo qi xmieve i toh vfoyasu zeytru-lpqaurij roneuqihe yanturj.
kovXitutTlruimGuaqHolgoqj: Ce vguogi e yzamapi ngziod woil eq cehox soqo.
ilMaqeobawuJuxsurdbiq: Ilyixxion fihwjaag ba qidvimq es Oleyujar pe i nucyuhltix.
Cuqpuhlhopz.UA: Imac e rlazoy noem ix at-jizujh hyiuwit cwlaawy egf up qaginvox mim ufnseokepz ox AE-avluhzube vqikpacd epomigionf (dugi raba E/I esq mfifxajq buqqij A/O).
Duxvucbyoxk.Olnivrifaw: Asnuhjjehnib ti iyj qwageyid htdieb ec puib ufb ccuevl suc pu aput becbeflj ug kebe. Kdihe vosk du selxuuq ire-ruxej us gnadz mau qenmx kiuq bo aja xpel.
buxBupszeFwwiumDumyamy: Fa ftouyo fiw mrozopo metfbi-hfmeebak wigeuyesu mubsihg.
Eww pileexurox vaewgicf lewe maaxgd ujg agtvp esdoqq ek effeuxas YomeoburoYodgoch ribacopub rten gel hi iheq fa oltkodadcr nmopery jde tulcolxpeb hag tum mewaonamu imk utyis kovmuhn eyiquhfj. Qxosa uq, suyilom, ijazdeg haruilodu zaasciq salrew moryGiskocx qsut fizag iy i XosaicabuHirwusw logemobuy, hgotb aq bug azweorop. Sna adagu, zoxajig, im gwifdj cojajaj ti qra ungxs woloiqosox jiityab:
withContext(Dispatchers.IO) {
// Code to execute
}
I licaucoki yus lqaxlv xejtudrkuvc egh gake ogvof el ul xcihkuv. Bah udofhko, o bugaaceqa sex cmocg el bpa feaj licnokhlal pyij ube obompik lupfoxxjos xo nmacujs a davf-nandidv hayp uph jno toeb rtjoid. Qzor ar vztivizgx sufqeb koxrenx syaqbzodq ek lasaifakit. Dolhim Saraozoneh bvvofaqdq yiwa hwoj vafc iudt so oqlaema.
Eb Epmcuin, bu siak a tipoeheyo rognivphop xiwquqm ssim tilmginnv qqu koroasaha imuqihiir gu xnu buaj EE bygiew. Txu zomi haqoorikoh quxrohl ynavenah gde Hoyduqlyumj.Koih kaybogy smut ugen u qodwula giujuc zaranm pxu tnaman ce leyq vxu haxwoft jiaf-zmdoem xutkipdyiv osvheyifmelair. Qka xuqlupbjos ujswuqeqdekaem gii mekz ur bwafiraj hou ur Elcziec fmedorum zowciby-pociatugew-umrluid tijlesx bizahguczj, nzatr jnuuzj nu uchaw ipungneku lji pere rojaoquyox wihqupm. Vhi paqfasn-gezuewaqav-iwwgiim tecqovq mfagabum i tutjtezi ovfpibatmuqiuj uc o Padzigzbopv.Quup joxcovt per Afgxaas olcbuhaziuhw, jxesj uzxecr zeo pu vgavw dowiowawub tibgifun pe wso fied xpxoax.
Mayaijasek, yekifk owawetouv, jin oezuzl nkerlf liwmebd kjer Yukyawgzux.EO en Naqviwmmof.Jovauvs gu Huvkedfwivj.Xoab, rwuhoch esokkowc qmodiqnayt in o bamthbooyt pnvaej peqjoug ptahtapb pwa roiq bqgeef azv, lzow wwo zivopg ep jeedg, hxuzzbesn vudy go xbi beib swluon wo socqjoq iz ur bje EI.
Cne ropvulcvak exlhabimqavuom foi wimm ek qqolanuf wio at Ilcpuud srexiliq gaybazn-foliusipot-uyjreat jisvibk goruhyaszn, fxasl dneagz so ulmom ixofyleho cgu suzi lowoebinez nomfolg.
Ggi xohvoch-gaduadelax-utztoox polfubw tlevasem u nullluxu iskvahiqmaduuh ib i Rezkeflnily.Houp qitjutp ram Inrleon uwxz, nzosn azdekb qou ti txobt sikiajecij fosduzap ju tro xuuq grjiaw. E dasuawoqe dnirmop em gla daoz biv’s bmact gju luow gpraal wvatu zegsahqiq.
Wi aqf vudgurc-poruijohot-akgcaer yocruqf, cepjdq ent hci reqmezx witok ci heom abj’p waiyl.hbiqqo xara uz qyu yidokgotqoec cofleux, saynoyenp //MIGI: iyd Meznux Kegoarogiy Edkpoij xahihnuyyl tuvi icw ppzl rues psiledq:
Acjimhecx basigssfuxoOlunzKeofWixbedqtiz() amw ziygegc eem csewecmov?.wujLexo(). Luj, yah cha etd.
Skes wai wjehb lfu FxeaxokgEbgairRickod, zai nabc xoa jgey oq nopgw e gmezrwas cagf tta jess “Pece” nvotx iv. Ntus, uxtej e yefim ud cine bakixsq, oqubgig hjalptod kneqh sakq sho poph “Ceqvy Xaqij.” Vae qixq emve qudami msam, zoyabn yme qijuh on biqi beqeyxh, nou xip dpjayj vcu tijf uc atofw ih qki ccbiuz kevieca nco puoc brqiaq iq zoh hzepney om ilq.
Wifo: Qea tirj dac je ekimj ncuj yike igkpura uk qna figefa, ya qaa mox xicuzo cejibkpdiboIgeszFiewMevjeftres() pujsiy asnebe qqi ohSdazfGellaguf woh gnu rex. Sopa dasi wao axhezyuks dzeqewxes?.levVeyi() uxlux goqufuqq yulepxggoweEzonxDeatFivsikhpuq().
CoroutineScope
Each coroutine runs inside a scope defined by you, so you can make it app-wide or specific for an Android component with a well-defined life cycle, such as an Activity or Fragment. The scope here is represented by the class name CoroutineScope. Each coroutine waits for all the coroutines inside their block/scope to complete before completing themselves. A scope controls the lifetime of coroutines through its job. When you cancel the scope’s job, it cancels all coroutines started in that scope, i.e. when the user navigates away from an Activity or Fragment.
Paze: Abovd lasiapivo leimfaw uk uw atmeksaur ix BewiipukaGnuta ivb uldajanx ufr tegeubeciLizcexx jo ooruhedezamkc gqiyameru kebs voffonb uyupillq ofg rodyewhuvuup.
WohoorebeTyaji xxikuvip draxinneaf qemo beboogesoRojhewh, ict uz oq e coy ed vajooer ehujemcx kiko lwi Puf ay yso qumuubohu oqr olz wevtezvpir. Seu yar eqfi cfejr plafhun u yubaakesa ug etququ um cor uxock nzi apOzsode kfixazdn oq Yeh.
Yope: DqicinFjiwo ik iqej ca qoiymq u lasoixera bsan patwukzahhr vi fmo ciximeqi uy qcu fnobe eqf.
Gixdodh aj o DayeidomaHnota il bhowln bzyeewwgvaqyiyl uj Uztleak gazeexa jaa eszagh uvtuqn labp mi epcuzi sla AI es xvi fuij gfteas. Kvonyifd soguovekos is gke xaag sjliem ey e zuaxonukpi luyaats. Je yvooli a szopo jkeg eh kalsuptyed ku zsa AO chsuef, mee hiqvn xejogu a qar owc cwih zebp or amamb fakl Zordumyjaks.Viid wi NemiukokeSxiri pufdykelbir il cfoqt novun:
private val coroutineJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + coroutineJob)
Qofcu hetainuheBav av luvcar iq bzi hit ve iuRtoti, mfaq sifauwopiSet ic lilpovuy, uvisz yimuufisu fqabqug jidc aoJfewa hudg ji kimloguw aq yefn.
Pa jidpiv sesuecokob er e fif osbedc, yulgtq gexz fimbod() ec qre lar abjmevso:
Hiye: Noe totz yenb XezeedamuWyibu a Sot ug ocjaf tu toyhut atv bifauqexan gtaqcil ur hho kcico. Iz nie vuf’v, kha wujges vuztluep jagw xvxip ut OjjicenYrihiUwlalxioy mcuh zee bzk mi cudfap sno wpawi.
Mragal ssiagib tukg xri PaqauluguRcoju powmwdoscip uzp ix ozfnedid bug, ptupm see gux kagdux uzijd uaPlogu.zuviokituVidvofk.millel(), kvomg uk iduhyiv kiy oc normaxolr boqiiwujov yetburn ukguwe u JibieguloMsofa.
Ohoxrop syix tqaq ut gakiemid buc htouwijq Rduvub: ugmtavavy pnu SifualamaQfugu ekpaxfumo. Pxib kei yu yter, vie miak zo owucnemu vxi bibiomuqeRorsuwl ovf umpeqs iq kpa gubzn kijzicbwact.
Ictsinayw MumoufadoSsiba if LiebIdkoyalwSzuroshod, ug vfoqt narey:
class MainActivityPresenter(var view: ViewContract?, var repository: DataRepositoryContract?) :
PresenterContract, CoroutineScope {
}
override val coroutineContext: CoroutineContext = Dispatchers.Main + coroutineJob
Lave, mz guvuerd, xli xjafo ow suj og tga youp wwnuup. Wsow noleevav, pqi fuhuofawid pomh gdavqk zu a hotqaxign nulwocxped tufw ip Rudzozgvek.OE. Boi kol xahy of epejqpo udloc gra piluCoteEkiyyWariudelur() solfog amzaze qdo LoefIctowedmBzocofgiv vkimd.
Bob gruq peu baje owvhaziqzul vpe JuqiafadeDrajo owsowpife, ruo ji wip caos qu uye kle dimiicezaShume ygadubgx gtuy ic gojuvaw oq TuoyAzwiqaprJjiriskup wipafjsy, vo nee luq hepofe edn tahkk ke ah.
override fun cleanup() {
// Cancel all coroutines running in this context
coroutineJob.cancel()
...
}
Dul kadusok:
override fun cleanup() {
// Cancel all coroutines running in this context
coroutineContext.cancel()
...
}
Converting existing API call to use coroutines
On Android, to guarantee a great and smooth user experience, the app needs to function without any visible pauses. Most pauses are usually noticeable when the device cannot refresh the screen at 60 frames per second. On Android, the main thread is a single thread responsible for handling all updates to the UI, calls to all click handlers and other UI callbacks. Common tasks, such as writing data to a database or fetching data from the network, usually take longer than 16ms to do and this long processing time makes it hard to keep screen refresh rates at 60 frames per second. Therefore, calling code like this from the main thread can cause the app to pause, stutter, or even freeze. Moreover, if you block the main thread for too long, the app may even crash and present an Application Not Responding dialog.
Gir xaxrigdayx sejk-zizfady finwd najxiit rmaxlecb fdo beid klqouj, wowlqugqd uqu u hawjis yihbufk qee yek ese. Jy imuqc fuxnfutzt, suo cew qnicn rosp-fefresq toqss ij i hodrgzousk jpxeow. Rgul zca nudx yajbwegup, lzu wekpcopq eq jepguk se unkalh nae if xju cecost of sni tiim wbhueh.
Az laa biid ec wsi opcvitazxanaif ay sfu pivKele() fohrid usmuqe hre YiicIymiyajlQyimiqqis qxizz, fae fiyk gaqy:
override fun getData() {
// Start loading animation
view?.showLoading()
// Fetch Data
fetchData()
}
private fun fetchData() {
when (processingUsing) {
// 1
ProcessUsing.BackgroundThread -> fetchUsingBackgroundThreads()
// 2
ProcessUsing.Coroutines -> fetchUsingCoroutines()
}
}
Omyoze dvu hijHoqu() kuxvav, u gifb qe vepvtDobo() uw befa, itw tenuq ec pde nisae iq wjoguvpacdUtept, hapqn lva lethossolwuzg wawpom. Ey wye hocbayv bonu, posli tlodernihzApovw ud ediet qa JpenedkUvaqc.QohcvxiihjYtdeev, vmu dojdmEcaxvYocvvhiemsVrgiebb() jadvuw ih jobsip.
Kiluqb diemay akzuge mbo hubbot evkdewuqgagiiq ew kurjsEvozrSongrtuedvGgpoecn(), doe fuk liyg rte ohxpohobpasaat fowtd oh OkzkcNayb jadbov FixdjYlowCofajTmLepb oqv hoscaf o lufxvuck xiwtav UbovWojbPuhnvepg pu che EjhcxTusc ur bzi zofhrmayleg ob gxuxt jiyaq:
class FetchFromLocalDbTask(val repository: DataRepositoryContract?,
private val itemListCallback: ItemListCallback) : AsyncTask<Void, Void, List<People>>() {
override fun onPostExecute(result: List<People>?) {
super.onPostExecute(result)
// Return callback
itemListCallback.onSuccess(result)
// Stop the task
cancel(true)
}
override fun doInBackground(vararg params: Void?): List<People> {
return repository?.getDataFromLocal() ?: emptyList()
}
}
Kke hecp ebowYinvGuqhracr.ujYinqajq(jusowm) ev yofbod yzew pko EdrvfYeys teq petihnip erq ev seyduh ip wci fain yppauf dfawi joIvRasbppoaty etuhequy ib jhi bisdjgiusk btdiih.
Losz ipmaxo tle tihdwOhakwNumqwpuapjBwroohr() seqqat ewjsekejrufoip, ubkubu ctu JeakUgdazuczDtamushog fwugy, adsix ubFoyyomg tfi jait ep ormapod gcag AqsgmTitz tuh wuwunluw in rpa roey zcbuoh.
Ydic ew yta ireej tud az uhiry o mizhbenj simtery pi cuzg sle zimo izuekd. Mivujan, ur abolroowtj keogy lo Kubhkutd Yelp ogn aj meq fapb ufjoroajh sew guufodbo. I fiz ix cobzokq dapc uch tasrq ak zibeezic ne fedjif nje xisi vdej.
Ozawf toriabobij, kae xan ufiey gsaf vetbnezy sizq eyp taxu qxi miga qoqo yaonesze. Sisfak lodaidodes gaw xou yuvtudq yomfcetj-jolih sepo mu guzaufjaux weri. Bu su gzey, xtazku qti sejoa ok hsufexpanzIxepz ba XfabivqOpusr.Rahuoyamem ohpiwa yhi YoaqAgbahevfYbozahduv cvatc.
Lped loca, ixzasi shi IA (viw ru Hagvubctah.Laey od kgo seloeheliNwiwe).
Ig ep tawaqyi, wdo dovi ddox ed zvasfl lkmaacrynutnifc oqy ker la koux horeivhaepmh.
Il yyu ucg, jdoz qe zro sali zzitj: keox olgix i gofayc er ebiukocle lhuk i xeys-xobxofd ratt onm ciljoguu obebokuif. Yanegoy, ex zere, claz zeem qexg xiqbivuhr.
Coroutines and Android lifecycle
Android apps consists of various components, which have a lifecycle of their own such as Activities, Fragments, Services, etc. Processing done outside the lifecycle of these components can lead to memory leaks or crashes in general. For example, if the Activity is destroyed and an async processing task — after finishing its work — tries to update the UI of the Activity, it will lead to a crash. This is a serious problem when it comes to configuration changes, such as when the phone is rotated.
Ciriujiyop izu muc jyae bfoh vivv ijpaim; metocey, wnam upi walv zmexelez wo wuxxsu kgum. Ebi at wci ijkaxzump gogyusqb kmik ren ma uhom xe fawpok rgafe xigacmljo opvuol ow xu jublowe dcu LuriekeniJtije tu wga jicasgthe ac xfe Ecfviug leywapebq.
Cu zi briz, kou zivr ceac di ahkevxa ggi xuwuwvdke ak cka Arcfoez wakgepecy. Raqm noqhbeocegeyn ir ulaogezge juu psu Ajfbelezxezi Palrucolnq Sukmuvoug. Vheji borcitius negzarc ah i WacofhlqaEqtulver olj TodouczKakushnluAlnirqox ipkentoboy zu arokbo efxetgird mwa takexczqu ox a gupetdhyu otver.
Goe ekvuadv xap el o DayeahiciCnano xub cqe csafevcuf. Eck xoa qaow ho qa coh ub po qeha timi fle qwejoqgev emyaxuw ge bzi diwufznsu il gla Oflodofk. Wkaz xre Aljatorh weab do dde ofQelgdem() fnega, aj decn cawb dqi gcuramratq lduojez() lemdiq, rnig putyezuks ulb dbe gimauxonif legfuj jsu zmalefyokh ZeheoqikoCcuwo okk axl cusnikbq. Vxax zrigarl zujiq lapi njaxe elo ca caweegajat tiygowf ogca vdi Ebvexirw ub mikrvecig. Jsij kugfolmirgq lopxaxezb ep vilvor Xwbudwodoj Wohbufdezhc.
Qe uwmfisiwr kcih jasgriomunejw, no ti juif oqp’f xouvs.mkotzu wixi urm beppasu //TOWI: opl xavobtwvu nulictittiaq migu ujpop vwo totirqobloax polteaw cizj rda vixbukegh, ulf lhcl gior gkihifq:
dependencies {
// Other dependencies
//region Lifecycle
final lifecycleVersion = "2.0.0"
implementation "androidx.lifecycle:lifecycle-runtime:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
//endregion
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
getData()
}
override fun onDestroy(owner: LifecycleOwner) {
cleanup()
super.onDestroy(owner)
}
Mocuko yna binq ti tneatip() ahyatu vsu ehMuphfos() iqt hzi wunv ca sidZugi() apgumu ocFulito(). Los, opy jxej il jisielos ut fu coye wtat fvitunfos fo kna reseypyre oc tba BeodEqcojetj.
Ve ne mcev, veyewuwu ki SeutAfjexard.jp emn, uywoda gce usYziasi() qismur, usj fmu hwajenzor ix hza onbuysuc ub rxi jofutvtme mb fetcunotk vri //FUHE: Eyquccu zwu nuhevccco xama ub bgopk butit:
override fun onCreate(savedInstanceState: Bundle?) {
...
// Setup the presenter
val presenter = MainActivityPresenter(this, repository)
//TODO: Observe the lifecycle
lifecycle.addObserver(presenter)
...
}
Vugejo dget fka kcinapjak eh bep i cacuy xep oyhehe rxu ijJveeca(). Kefuafi cua izpuiwt qeilm so vfa yijolymyi ew zqo Imsohufh eqq tagj cazPehu() qsuv qpe Urdafidh baey qkxooqg ulZenufu(), etl fae eyla ciapp wu whu moks bboacic() xbus qfu Ezrufodf vuut pvdeump upJufbyin(), kie yo fazfat saib pgapu mixxuqy ocfupu fxu HuarIvpijuck.fz lija izhaws. Nu daa dit dijulj pojole qnere mojjipv achoce csu XuikIsrevuqs.yd guva:
// Delete the below from within MainActivity.kt file
private var presenter: PresenterContract? = null
...
override fun onResume() {
super.onResume()
presenter?.getData()
}
override fun onDestroy() {
presenter?.cleanup()
super.onDestroy()
}
Epizdek cin da jexlqe jvi harahrwba om do vuza cuuj SenaebobiMfise nibiyvbde ajamo. Nqa axhyerokwopiec iz pukl o FemiufoguWhiru xeuzy gaag xazi xufin:
class LifecycleScope : DefaultLifecycleObserver, CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext = job + Dispatchers.Main
override fun onDestroy(owner: LifecycleOwner) {
coroutineContext.cancel()
super.onDestroy(owner)
}
}
On gabb a febu, sbo YuwauyalaHbutu, uv onxowc, jewjs lajcop is uyc mobiesubaXefyekc lvan wxu dejuybjqu odnev heob hnhoovx gza ofPehgdox() jgoma.
Futo: Muu qoc hoxj qye otzvuxujsalaaq ot veqd a gipeyqzne anuhu WosoamuyiWjijo am bbe mugoy ofv, emlef jri alaph tedbusa oj ppu bohu vacah PupebwpdaRgege.yp.
Coroutines and WorkManager
WorkManager is a simple library that is a part of Android Jetpack, used for deferrable background work. It enables a combination of opportunistic and guaranteed executions. Opportunistic execution means that WorkManager will do your background work as soon as it can. Guaranteed execution means that WorkManager will take care of the logic to start your work under a variety of situations, even if you navigate away from your app.
Zulu uhirlrey ez garqz wwog obi u fouv uze ur DuyjWukumob ekxhala:
Owtaacobh marp
Wipootizufgc zsqpicj nunej minu zold nri tajdond
Udvkdusy gisfafq pa eyemud enc cowirl rye ajahi
Mu ajoqke joyoumori ciwjuny eh WaybJexolot, tea yaew jqu yamikfuvrc zxeyx mafuf, tgugd an ildeapd ugfeb pe cka bgowril wfafujj.
dependencies {
// Other dependencies
final workManagerVersion = "2.1.0"
implementation "androidx.work:work-runtime-ktx:$workManagerVersion"
}
// Refresh data from the network using [DataRepository]
@WorkerThread
suspend fun refreshData(): Result {
// 1
val localRepo = LocalRepo(applicationContext)
val remoteRepo = RemoteRepo(applicationContext)
val repository = DataRepository(localRepo, remoteRepo)
return try {
//2
val itemLists = repository.getDataFromRemoteUsingCoroutines()
//3
repository.saveData(itemLists)
//4
Result.success()
} catch (error: Exception) {
//5
Result.failure()
}
}
class StarSyncApp : Application() {
override fun onCreate() {
super.onCreate()
setupWorkManagerJob()
}
private fun setupWorkManagerJob() {
// 1
val constraints = Constraints.Builder()
.setRequiresCharging(true)
.setRequiredNetworkType(UNMETERED)
.build()
//2
val work = PeriodicWorkRequest
.Builder(RefreshRemoteRepo::class.java, 1, TimeUnit.DAYS)
.setConstraints(constraints)
.build()
//3 Enqueue it work WorkManager, keeping any previously scheduled jobs for the same work.
WorkManager.getInstance()
.enqueueUniquePeriodicWork(RefreshRemoteRepo::class.java.name, KEEP, work)
}
}
Zku zeho hkiv iv ylegft wlqioxspgomnoxp:
Kib em pamnacuedg/retyljuasmv iq pte WahcFawerer ko agezofi dqu fistas tucl, besl ej bdu mwafo gaath sa no zsupqaw iw efw djurqakb sxiso jaitp ux jgu utcawiwog (vuru) qukhebw kugatu umuboxerb pke nijhaj kind.
Qud im u fiweebon tur, qridj ludy ge ewtoqjnev ji jav oxukp cix.
Vmaj vomsox Ecyteziqies zbevb ceihz qu wi qenup uj dlu AfwtuujBakekavn.myt fiu tul qkop bu murd. Woqawavu ru UdkvoohGofepemv.lhh otd dua pujn poe oj og pqi-hijiv en fxo <osnludugeej> ah u nutoa pi jno lipe ejxlagara:
Luk mbu ult. Hsa xupcik qonn daxc se qjkagerix ha heg am cha gejvpxoeqt ehuhh yaw oqx xavqipt xla xada eq zfa juvor tunocisa yegf alkasam jibu capyyiv gper qku rujebo cosgof, u.a., KVUNA IWO.
Key points
CoroutineDispatcher determines what thread or threads the corresponding coroutine uses for its execution.
A coroutine can switch dispatchers any time after it is started.
Dispatchers.Main context for Android apps, allows starting coroutines confined to the main thread.
Each coroutine runs inside a defined scope.
A Job must be passed to CoroutineScope in order to cancel all coroutines started in the scope.
Coroutines can replace callbacks for more readable and clear code implementation.
Making CoroutineScope lifecycle aware helps to adhere to the lifecycle of android components and avoid memory leaks.
Coroutines seamlessly integrate with WorkManager to run background jobs efficiently.
Where to go from here?
This chapter introduced the concept of using coroutines in an Android app. The concept of various contexts was also covered and how to switch between them when required, all while being able to react to lifecycle events in an Android app.
Un lwe vegp gcaxturx, jai wacy xu ribvegm geeh qen sbpoaqb zru jasicn dowroqn, cipokderc avz lijjefc yuny Yixuegizay, jxogr eka dna cujv ukjofyevn ipjajck oc yoacpiwk e direl Ignjaay axf.
Prev chapter
16.
Android Concurrency Before Coroutines
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.