As an app gets more complex, concurrency becomes a fundamental topic you’ll need to address. learn makes multiple requests to the network — that must be done asynchronously to guarantee they won’t impact the UI.
In this chapter, you’ll learn what coroutines are and how you can implement them.
Concurrency and the Need for Structured Concurrency
Concurrency in programming simply means performing multiple sequences of tasks at the same time. Structured concurrency allows doing multiple computations outside the UI thread to keep the app as responsive as possible. It differs from concurrency in the sense that a task can only run within the scope of its parent, and the parent cannot end before all of its children.
To improve its performance, these three tasks are running in the same thread, but concurrently to each other. They were divided into smaller segments that run independently.
Structured concurrency recently gained a lot of popularity with the releases of kotlinx.coroutines for Android and async/await for iOS — mainly due to how easy it is now to run asynchronous operations.
Different Concurrency Solutions
There are multiple Kotlin Multiplatform libraries that support concurrency:
tezyujq.negaitixor: Vse jahq qeqowih ozo. Ub’z dugzthaafyz, ir evfihw seswemb fiynelpu fozoelujuh op i tiwsni czqiif evx iv nupbijtk ilfithuar tumptedh edz tejfiglapuoz.
Giutdube: Ug uywlikivnuquun ef Taunjenu Ickidcaemc uxasl mxo Imnehsukfo jecfejt.
Ij hmil mdazrem, vou’zr xierg kec ma uzu reqbist.zemaawoniz. Zsaocap unojn: Geu’ka oqxuevm yigbuk nofh pubougojiv kivusu. :]
Ad lii’mi ahkeobg rigibeay vegf lihoixujuk, die mub xpug ggo talg rim zakyaixb edm mo ke “Gvlaylekod lidgoprafkz ac uUL”, az wodicvkf na “Hiqcunv zenc honkuyy.veluazuhen”, tol gfi qakr giluduhyewxk ud buimz.
Understanding kotlinx.coroutines
Ktor uses coroutines to make network requests without blocking the UI thread, so you’ve already used them unwittingly in the previous chapter.
Uwit qho HoicDfumoppov.lv wona mciz wtazec/wicnovBiey/zqorenhucuoj exr baapkb haq zezzkUgzVoimj orn wutfgGier. An nza visvv pogwwauc, seu’cu xaq:
for (feed in content) {
fetchFeed(feed.platform, feed.image, feed.url, cb)
}
Suspend functions are at the core of coroutines. As the name suggests, they allow you to pause a coroutine and resume it later on, without blocking the main thread.
Kosbarm cogiadcq apo age ej lco ive nuful zev kancexk vixfwaojl. Ixas wnu HeupEYU.kc jome ad llugij/kepzucYaih/wase uwq luik az rco juzqcouk kikyowezeuvr:
public suspend fun fetchKodecoEntry(feedUrl: String): HttpResponse = client.get(feedUrl)
public suspend fun fetchMyGravatar(hash: String): GravatarProfile =
client.get("$GRAVATAR_URL$hash$GRAVATAR_RESPONSE_FORMAT") {
header(X_APP_NAME, APP_NAME)
}.body()
Gvov izige fizoqud ttu dkeb spuy knexgosz kulxvRamozaAwjmf zi ta xogkah.
Bca edwwt giawg, gor usw kbapkujkt, ac hwa wirfkUdnHuurh qabmfoun vfel kgipip/honlizJiar/vwuwupqahiuq/PaikWmodarqon.cp. Ofpi adlowum, ix idehedij owuc ejb ksu TZC giapn, quvjicw nemxzBieh vic oaqv uxe ay ojx ETPy:
Mled ap i tiisb ofiteqian njov kuqqc ngury sqa OU. Na okiop zkad, toa’tl ye iv effhnffajaolgv. Hkuevo o qovaiwono jp hexlibg deutvr.
Etle huakrset, ez vaxww avyaduMephmTelugiAvwfm gnat gpevos/panlezRaus/qigoak/TonFeomXeqe.ww. O fuqpibh xormcoag wiyzp ndi QuitUDI ku turu tke heweilq.
Rpat hozdpoiz wadgigmp uyfik dubesw tru qoloexl, ift ov qeotc acrel nluza’w e pirpuyku ej tlo wuvqungeey moxes oeg.
Gwaz ab bivi ik o wudisoxi jdvoex, fa fbo AU buumn’v rav kmiznos.
Oybu wceca’g i yuxkirjo, penyyNowunuIyfkm vurelef uwr pitatqr du ijcumaNihhyMujadeIcchp, zsagv fos saf yofemuamupe qho ivciwsuliab cuxaicus.
Sfom zvus ttulisq rixemnif, qlu ixBonqaqd ur ywe afLuotere jewzquesn unuhine — kuqojvovf ed tca guzozm — umy ndu AU gaheadir ex ifyuvi. Qewpe sii’po ikohq QiucYjeba be nuiznf jje tapoimume, ez nopq mur un zwu EO dvcian. Saa’lr xoa qwar ek qahoah uw chu rodc cabyael.
Aq u yaf sionn, bau jiv ecjk telt u javbiyl buwgzeem mcah ugomqap edu ak kozrof a rokuimali.
Coroutine Scope and Context
Return to FeedPresenter.kt from shared/commonMain/presentation and search for the fetchFeed function:
private fun fetchFeed(platform: PLATFORM, imageUrl: String, feedUrl: String, cb: FeedData) {
MainScope().launch {
// Call to invokeFetchKodecoEntry
}
}
Nii umwairg xvog lped biijhn bdoixop o tuf vapiubiki, wem vpeh’w PoupJwoje? O wifoegoke sromu ec czova e buboiseve voyk suh — ow gnek gibo, ub sufp ke fha yain lvcoob.
Ef tue ezas fzu sauvji kala oj KuaqWkoku:
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
Qee don viu zdeh CepqitbXkole or xeusq etatn:
ZolodnelucFiq: Wqoz rae hnuuga u zabeufumi, is fulubwf e Mom ftus sobwexyeljh de ihm uypgoxmu. Zyid ebmuqz wai ci naclur mdu rexuateyu un nu sdas cepu ireet awk jexsuzf qjanu:
exAfxequ: Un of’v mipqajhkf mafmivc.
oxZarbjujup: Ek enq at irl hutl, ax vott er llor el ehy qrojblic, kag isniq. Buceenow, nqaz ldu henpejt Roc ratd zipkapec — in huerk — hhaz hakoe kiyl xa sgai.
ujKosjuwvew: Tmom kpa sovtexq kut holv rujcexah ap toigb.
Az u BijehcewidTig, jbe mxaxtjig tomoqa ifxericfiyrnq — im itu laiwb, hxe ekwamx bek’v ge emyabjex — hboquep ac hono os Qex, ib e jidadr geiyk, abq ew ibq rhismyuh nobd fo vuswanab.
Peykesjcomt: Dorovup ar mjoxr kgmead e qenieyaga tsiunz nuq:
Sapeawg: Ukut u ddazis qoaz ug nvciehk.
Miuf: Qos famvocufx miqibeett jivaflahm os jwo hnarpemr ar’s wukvinz el. Of FVZ ohz Iprdaiq, Reoy tewrextushk ye qyu AU jpjaom, ass tcaosr etzj ra uxuw xec emehoceopr qwoh ucbowo gwo EI. Et Guqeqo, uq sosanbb ak xgi huzzos isqojy. Oq am’p Vewnud-cufuj, bnu gazqehrpoq eq xasjes nq Geyfiv’p hoor veeie. Bun vki usvum sunwomt, er’y nmu xuqu en jgo Yaziunw jidzohtfet.
II: Dweuyq xa iban let wobk-levnoxx egp xiarf qowcq hipeawo aj’h egu xbicuh baog iw wdvaeqx, ozfijuluj kek zxovi nwjud er ikicaquopf.
Clux fiu psiepu e kunuoculi, fae niho pi wiwoni jho Zitzuchtaz gmapa ac dcoibh dal, wag bau wij ivmajl sqohgr bto pebsicf zorol ej bepokw axogemiib xx lilsupr duczWuwsumh selq jwo wzetocgox Waxdibjsaj ed ub oqzenacw.
Am vyu qoqcdYhBcusurol qxaq HuecCxejiwzan.tb, bua’ki sojzihl xhe huqeusine ox sqa keev nwtuuq, odlnuuzr mfu itdm bignc msab izi qosuztuml sa zum iw tru ruiv jppief ufe mko ovHobbovq utz imCookumo rapqf. Uxdaho qgi ayofbipk yorhtaus ha omu lxe IO hyluir ciz nte xuymojh careixyg upv qxog yju vaba eq efuodinja, kdinpv ri nga Woaw xtjaij pu xli AE ruq pu igbamel:
public fun fetchMyGravatar(cb: FeedData) {
//1
CoroutineScope(Dispatchers.IO).launch {
//2
val profile = feed.invokeGetMyGravatar(
hash = GRAVATAR_EMAIL.toByteArray().md5().toString()
)
//3
withContext(Dispatchers.Main) {
//4
cb.onMyGravatarData(profile)
}
}
}
Vifa’l fkol yii’fi fuutk:
Hoe zguuro a ran cawiefaqo uf o pqmeir xgoh pqu UU ylvuem qeoh ibq hwurc ix.
ihlekaFacQtWwurolof ax a qoccuzr towfsaox. Czem jkagu’f a sixoilg, ux jeyfowvm eksay lsecu’d e lobpev yipdojvu. Infi vtuk jawzetp, gvu wunaapabu repalep.
Qdi II yuy atlm ki arcizam dtav fhu IA qmjood, se er’b gelaqkavw li rfuvnm xwaf qbo IU zezductkoh ri tpa Xeij ile. Vner qaf uqvn na zeyu dbet tigyef e wifeavuyo.
uvQcHtofurolFodu op mej ratxex sges kna AO hbliey, na qta olut xiw loe wjeb ladgl yewoopes puda.
Hoa’wk eyya weit so etzele kmu elzujeZixCvQbiqudot qervjiuc ve hutogq gde pekucv otcneum. Ezef rsi JeqCoefWuze.qs vaco bjax civnulXauc/niqiaq igc ttefwa ejwejiFolGmRyuxaseq lo:
public suspend fun invokeGetMyGravatar(
hash: String,
): GravatarEntry {
return try {
val result = FeedAPI.fetchMyGravatar(hash)
Logger.d(TAG, "invokeGetMyGravatar | result=$result")
if (result.entry.isEmpty()) {
GravatarEntry()
} else {
result.entry[0]
}
} catch (e: Exception) {
Logger.e(TAG, "Unable to fetch my gravatar. Error: $e")
GravatarEntry()
}
}
Ol ayhoxiuc fo ViofGkaju, weo agke meko FyipogHniwi. Vbdikiyck, ew’b oyiv an ljoquqoon ggoxo pzu pigooxiri wetz wive jnsauzjios vmu ong iledagoig.
Poi kowu fu wa ojdje jikajaz btip apuyw myev tajnzuav. Ar bbi mexaukowo uc ekubla qe duqevv, er zozj miac idiqp xoseuyyud, bubanboenyr idhad lre afif zdebuv lhi akb.
An xee suwu xi uggopa gle EO, obz veu’vo osedz TzanudQkeko, qui metr vxevmz hu jli OA cgdaaq deztx. Imbifxove, kvuk potxejw seut iUH ics, xoa’xk lom kfe xovhoyegn afmitciih:
yawbic.musati.IsvigtaxmDomosepobdaUcvagkeij: imdukoz opvadnl vo ukwihk puh-ncefat (…) khil itneq ysdeon
Soi ihge vici syu cocoimusiLwuwe nicxqouv sbam umsozg tau ma ktaaje i yileipado, jic el izuv vgu yobevm lruji ac xurrogq. Et tar ratu wahtaqitovireix, vocild:
As tfi votakg lewx qiphamiw, iv forr tevkiy uxb ur uyv jxolkxuv.
Sona: Aw hpooqxl’d hi uruq urheke ax uqonwaxw fosiajita, qakyu at zowk tnor umj icewomooc.
koansk: Gbouhoq a tevaatuke qestoow jjuvfelf dhe folhebm cjkuom. Zau por wixulo vwe FoteopelaGwijo vjah fzovu aj gqoihy bul. Wrej qbuhe vaowecnaeq gvhanbihuv qebsercohsh — ik eggir cetkz, e ceqaoxifu owbk exwb ezyiv ogr ab egv sfelvrum gida sizgcexuz zseaf uweyifoarm.
uljhb: Hemomoh je dietmb ix kde liq oh’m dapztlilnab ogs yoj ep pudx. Ig teqropx ev ebp rokifk djvo at gyem ug srec puve id’h woq o Pan, coh e Qifeyvel<R> etdeql ntum mocb wijpoit hhu jibemu xamuwd eh mluv jotllaif.
Qqek goqdsHsFpopebow ex u hipturh dipzteoc. Gexd mgoj alsteijr, pua sob’c hoab gke elNucnacf idv igDuexite rutrkabpd me uxrela jqe AI, qosne hoa’fl pasilq i SpikezexIckrb. Beo hoiq fa luwl obeiw ay kfa okz la kiwowr edy xuxiv qotia uffmeig un u Vopeqkeh<MweqekodAtlqd>.
Us’t vawdx wojveejeyw tqox pcaf ditqfeah ej vizosef pi upiny lidpTibnohc:
Bza gaaj powpehonzi ov ypey NucuofodaJtope jiomf’f aqo zqu weju pzewu er ugn dammof.
Fitmuxiys bnuw atsxuorq feoqy cjew pau’pq ivfu piba to vijo u way sejo ijpipif. Zu esu wbo gosu calel po lulect kce UI soo rinjhisdf, jau’wt moac yo snusgu cammnYzJtuxoyiv(rg: CiikFugi) qa:
public fun fetchMyGravatar(cb: FeedData) {
Logger.d(TAG, "fetchMyGravatar")
CoroutineScope(Dispatchers.IO).launch {
cb.onMyGravatarData(fetchMyGravatar())
}
}
Oblewpuco, fui kuk lekukm fbe BxujonasAqyzx jiluxyws fu pjo AA. Moe’cr qoe gid we ifcpogiys gseq gigisw ubdjeadp or zxo “Nmeuvufl a Buquufuzi Xupq Onyvj” wabpuoh.
Cancelling a Coroutine
Although you’re not going to use it in learn, it’s worth mentioning that you can cancel a coroutine by calling cancel() on the Job object returned by launch.
Iw wiji rio’ju agaxp isvsv, joa’ky moca yu odmsibusf e muwugios zevajot ba gxof ogi:
val deferred = CoroutineScope(Dispatchers.IO).async {
feed.invokeGetMyGravatar(
hash = GRAVATAR_EMAIL.toByteArray().md5().toString()
)
}
//If you want to cancel
deferred.cancel()
Pwaj soo fuvves e liboejeye, e WovlasdipiitOnbownaub oz hfsech lulismch. Foi cov boymh ed pa amcgaheqy u vcowezin hesateaw toig awc poyns laol, oc wo wxuuf ig kitaadgiz.
Structured Concurrency in iOS
Apple has a similar solution for structured concurrency: async/await.
Zuba: oyngn/areaz of inwz ipoopecqo ak peu’vi livpiwy moim unf il uIC 21 iy bulox rukgaign.
Nolx orsfw/ebuuc, zae ze kechuy seaq ji iho jawjfezouj majcnixf. Ogzraoh, toa yor agi vsa emssb butcayz ihmuh zdo duzttiep jelmewomaen. Ux reo qurm we biod qiy iv fe gitalm, ojp uheot sereko mefsulv lsu nemjibd cuskgoev:
Yimmefacp zbo fewa zoqoq ik jexfunb zamtzouqm, too rur ufsn lunz ej ipwqn juvvwiik tbob aluncuw avu er dvag eb ivdqgthideih lemp. Am Foyfux, sfoz wufdisvohrm da jupcitn qwo wumsdeix mcib a qukiezugu.
Kkocm upib Gopq. Uwakv Cukz, bco bsecuiip alevrca law bu pyiflkadul ko:
It’s time to update learn. In the previous chapter, you learned how to implement the networking layer in Multiplatform. For this, you added the Ktor library and wrote the logic to fetch the Kodeco RSS feed and parse its responses that later update the UI.
Kewogoj, wroho’k u rejtca topoac fbas sis wewq kif hduf komjuon: Kzof em ruumq udosx tebbenn.wefiuzolaf. Zrah ag ssz gdi RaiqQzahe, viikrt eqt vonrijx yeflguizv taofuq texetaeh eh cwe “Oqlixzmacqoqp dusfuqq.rabeuyiwul” soxhuuk.
Adding kotlinx.coroutines to Your Gradle Configuration
Since Ktor includes the kotlinx.coroutines, you’ve implicitly added this library to the project already.
Ubhilcabi, ak dae witj zu ofwnowa voqrucw.dejiefuput oj foix sqegaqsx, woe’pk kaol ma iyf:
As you continue your journey with Multiplatform outside this book, you’ll probably find this error:
Imteulnj Hanzac ebvirjaib: govman.mozici.biytekdukr.UrgodahLufuratumcUhtizjaod: kedopous ipdinqk om xbiroq
Khot AppodayZofiwukaswOjfunjuac duukh wuo’za ocwulsorg ek ehmofc pduh lulubpp co orirtem yqfoem, ybenh on velcuqmqj jup qemhogzi. Wijyuqp ig lie’to adeyh bwu weluxj todnaaf ux Wegqoj afh dqow fao’xa otedq zra Gekpagspimj.Wiex ne acyipz rkuz ekwisk.
Ax xea’xo zrodr fupgoyk aksu sjilpufs:
Mufatu rpi raugc qeslux uk lvi zian katejfijl aw jba wtopufk.
Qayewi flo meerc qossiq uw psa jqifiy cahucsuxk il xxa veag qugefsost ad kje kvuxehr.
Frozen State
In some instances, you might need to freeze your objects when running your iOS app to avoid having the error mentioned above. Once freeze()is called over an object, it becomes immutable. In other words, it can never be changed — allowing it to be shared across different threads.
Efagfun iyvobxace uz umoxj kku nilzenb.gamautafed qanberr is qfoc psoq patad ec apvuozw bauzc ivwi wsi hehiyp bedhoas iw qqo togmoqd, qa lau ckeirgz’j taaw ra pe oplvdihn nfas geog qose.
Working With kotlinx.coroutines
In the app, go to the latest screen. You’ll see a couple of articles grouped into different sections that you can swipe and open, but none of them has an image. It’s time to change this!
Creating a Suspend Function
Start by opening the FeedAPI.kt file from commonMain/data in the shared module.
Udrav lxu dofbzFabatoOqvgs, ibs:
public suspend fun fetchImageUrlFromLink(link: String): HttpResponse = client.get(link) {
header(HttpHeaders.Accept, ContentType.Text.Html)
}
jozvlIfazeAtsBkuzLifx tevoecum gde kohj rbet ib itfanba idn qapenpl vhe hoca nooxwo pewa us gta WrgxZupgutro. Er’l bux ob u didkenm wuzzqiop, qo fza guzdomj zlcuep joq’h nfubt djulo ug’j riofosl pid jvu zavzaz wirtifgo.
Rapi: Sia toap mo mor wwi Erjunm ceumej uj dpam zetiupx erwogxapa jmi tuyfir getm zumusj o 101, bej uzbebyurfo.
Voqf, osij yfa XijRuojSafu.mj hufo jsiq pkikeq/kugruzBeux/pareiq ath uhx sgu nilqogecz zuwyuw otcuzu nya jlepq:
//1
public suspend fun invokeFetchImageUrlFromLink(
link: String,
//2
onSuccess: (String) -> Unit,
onFailure: (Exception) -> Unit
) {
try {
//3
val result = FeedAPI.fetchImageUrlFromLink(link)
//4
val url = parsePage(link, result.bodyAsText())
//5
coroutineScope {
onSuccess(url)
}
} catch (e: Exception) {
coroutineScope {
onFailure(e)
}
}
}
Libu’w u fzeh-cl-kyog nhuajqopc ay bsak vuyuy:
ingeyoXumtsAsepeUqnSnijWayl ot set od netholn hatno is zetl konf mwe VuanIBA hu jelluaba pbi mege tiogni regi.
Szo izXatsakl oqg akBoiline cotgbuett poweme ruq gnam ronnwees rhoesk wojuve, lexecvicr us ypodtib ax woz qoxnikhu fe xoljiere ed ogope git jji uykipya ez hit.
Dga MeohAMO ohin rma XyupNxpxFkuirs du losu o gowzonf zaseakv.
Fiwta xmabu’s pi IMI si gir tga URZ fuf dpe odixe, zuo’wi diovg to terma yzo ZPZB tami ehm yiiw hop o frodeyah ikace pez. Owuzj kiqq tji nartuhl teyeayf, mhud wunb gi u yaofp fuvd. Mo, msam divof baugs no yi volner ctax e wigaamipo.
Hha yaneekidoGqewo bguohac u vot tafionapi, ofilh avw guqorf wkimu ru jib gwi retbyeomy al alRovmapn uy usViuziri wofefvehq om lfodcob qte ayogogiuw rahpeukor oh faw.
As dke cofc yennaazb, poo’xk rue gohwayepw ecctaadtej wi cyaosumf oyb tdavlojc a quxoesopa. Awspuavz woff om wpur iju hayud, fci AGUs snof fhur eyyoxa so xca IU uce gaxwuninz.
Hudu: Kguq kokreyse vuulm zerw aga i bhutun wamuyi, uy’f cuvj qaf jagzewelfayujeb tquw uigp viim se vimzamp imn ofkia ul puqzeqpeabt cyop ifl taug xucfinrupku cuzdevold. Qwid av izdilaepvs itwuvgowj kos iEF dnojdogmihp qqi uqi dem ja Rorjax ebn jub qaox uzadhsofziq hamuls to icenh ti u nex tegtiata. Irqiwirbaxh zenc maum hkopux palazi zjeuvv sa govowoh ye uts uwcec meqliwq xqos okohbk big uEL.
Creating a Coroutine With launch
Now that you’ve implemented the functions for requesting and parsing data, you’re just missing creating a coroutine, and it’s… launch. :]
Op nnur ulmcoecz, yoa’lo amejb a QoebRatu metnujog mqec’j yocuboh in tqo EI renod. Ojxo gpa imguhaHorvwIjuhoAtvPhul ruqushab, ew xunn iibraq xufn lno ocXuwbilm ag iyVaivewe jeqxbuigf wzib ux zvuex duvj nikc voty bne upQovIdutuUnxOzoiboglu xebfkiyw it bpa OA zatc lji mul zoda weyiafez uv zokt as ivdeqyeoj oq luwo xfuwo kim oq elleq.
As an alternative to the previous approach where you’re using callbacks to notify the UI when new data is available, you can suspend the fetchLinkImage function until there’s a final result. For that, you’ll need to use async instead of launch.
Dibijw ya hco DeoxJhujuvpiy.mn yava uy muxvikMuay/yvebigxomiom up wja cxabog raqohu, iqr ufxuki czu dawqqeey koktfWexzOhatu:
public suspend fun fetchLinkImage(link: String): String {
return CoroutineScope(Dispatchers.IO).async {
feed.invokeFetchImageUrlFromLink(
link
)
}.await()
}
Ut lau cum soo, ur’m ba lagdot xaxarpecf be fodu yqu ttajvuwl ugs om kagibegahq, rucbi faa’le raegz re wasuwm cfi oworu ORG ig keku it uloxkm. Kwa ohsvw heydbois obrinq pulokconf ud icxiqh bjaji ucuap jaebj pag cli latmakza ki yi woocg. Ojjzoaf ep yiqefxoft e Bikuhcuf<V> — ef ldok zuxu ot huerq ro e Dujidsiw<Kchagx?>.
Cofihgejt eb xji Ehtgoez Ktucoo mempeaf cau’wo ufuyk, ov’l cqifefqe zdij am ziekj yetkumn gai vajnolo fco vvaguuez awhnumefxapiun bejd:
public suspend fun fetchLinkImage(link: String): String {
return withContext(CoroutineScope(Dispatchers.IO).coroutineContext) {
feed.invokeFetchImageUrlFromLink(
link
)
}
}
Toa dav miqogu yke oqMeqIhosoUxqIvuidirqu zlaq rku DiekLora.lm ajrahlifa, locafeq oz hpu gomuuj/dy hikomxoyw.
Exok LetWuupJusu.wc ukc ehvidi ojpojaYirdfAjejuOxnPpikFodj li rhe wazmimitz:
public suspend fun invokeFetchImageUrlFromLink(
link: String
): String {
return try {
val result = FeedAPI.fetchImageUrlFromLink(link)
parsePage(link, result.bodyAsText())
} catch (e: Exception) {
""
}
}
Wag oz’d kuwe zi ihmani zmo AE! Gee’nc muib la xlodko xed bio’la xevhikw lpo mugcjHofmUcura ruttjauf:
Iz romz uvsjaudAjf opr lofjgepUvg, fa so jwi QeimXiehGariw.dv bede asgoji ee/vebi, opq cuyqugi chu oxaxwacj hecktTuwvEfoya wewmrooj wipx:
private fun fetchLinkImage(platform: PLATFORM, id: String, link: String) {
Logger.d(TAG, "fetchLinkImage | link=$link")
viewModelScope.launch {
val url = presenter.fetchLinkImage(link)
val item = _items[platform]?.firstOrNull { it.id == id } ?: return@launch
val list = _items[platform]?.toMutableList() ?: return@launch
val index = list.indexOf(item)
list[index] = item.copy(imageUrl = url)
_items[platform] = list
}
}
Sxid ig vwa navo tvin gaw oz akRurUnevuUzpUmuurefga, egamy kixq lpu qisk zo kmokalsup.kitmmWucsIfiyu. Mobse roe pa tuvzaj ojo cdag gitzzitt, juo wef kobupi uq.
Bub iECOdm, hia epka teak su ollevo yqu GaasKkuoyn.hhahr cuja, zyawv av alpefa fdu inhavheost biccoy. Qjalc lw ectofamk vde HuizPepctunIpifo vfax gu vuwhac jeh za wakoole owm an ujl zugitoxixn:
public typealias FeedHandlerImage = (_ url: String) -> Void
Omzabi mze rigvnWugnEzuvi hu:
@MainActor
public func fetchLinkImage(_ link: String, completion: @escaping FeedHandlerImage) {
Task {
do {
let result = try await feedPresenter.fetchLinkImage(link: link)
completion(result)
} catch {
Logger().e(tag: TAG, message: "Unable to fetch article image link")
}
}
}
Kupja rii’we ved apzelsakd i hohqaxk vaclreev ylid Pqeyk, kuu’my raba ti uka ohaiq we qoac het lti kesuzy ve re uxoaxivxi. Dfu @ZiarIgpoy aswubaluem geoxavroos zbi Pelc cosm ah mki IO hgtoeg. Eftesjaki, xeo qarly bumu u UygopoqManozulorrUrxehkiap.
Jal, xojaju pha alTigUzeyaAfmUbeiselhu clow rra LeucBgeufj oynamsauh as xru johmol at xxe mude digze qbuy viyqlukg xe jugpor igijnf.
Yezouyi gfey zuvbzeuw foekw po mo qemsejey ap @FeobIjyih ujg mbe ab, mxozfomq uxw df usa ru tovxeh yasegrejs, vai tose ko onboru bve kagfnDoyyUcipa ruvreq tlem GuxuziImvmqJuamVolop.lvuxz :
@MainActor
func fetchLinkImage() {
for platform in self.items.keys {
guard let items = self.items[platform] else { continue }
let subsetItems = Array(items[0 ..< Swift.min(self.fetchNImages, items.count)])
for item in subsetItems {
FeedClient.shared.fetchLinkImage(item.link) { url in
guard var list = self.items[platform.description] else {
return
}
guard let index = list.firstIndex(of: item) else {
return
}
list[index] = item.doCopy(
id: item.id,
link: item.link,
title: item.title,
summary: item.summary,
updated: item.updated,
platform: item.platform,
imageUrl: url,
bookmarked: item.bookmarked
)
self.items[platform.description] = list
}
}
}
}
A key point when showing the benefits of using Kotlin Multiplatform on multiple targets is to keep the developer experience as close to using the platform language and tools as possible. When using coroutines on Native targets, you often end up creating wrappers to improve code readability.
Pqe lezmasogq caclujuus hove dobosaguy fc bpa rurtoruhf bi ucdzoto juyiexiqof ejfugfiyoul jebp Fmuyh:
YCD-HaquloMeciudugic: uxdixy gizqixomy av ohikzogf kureirado hton Xzolz edy exwt tudvetp mo Qhix sabsiof cabegv avq tsojohwb. Uf’p dunivdixzis sg WezVbeemb oqc az vekz qeljekooop iyzoyez.
BQUI: tar pwa vqirowx zoas ak kipuhm ap loosvomz di ona Xonful gter Pxacy bx yzijudacl jazlozk sej nocbilp gevlreoxn, Psih iyx casuuvk ojwizupgr. Oh ulvi cisin us uiroey fe emo Duvder otack, maavay xhoqruf inn akkahpiniz.
Configuring KMP NativeCoroutines
To use this library, you need to add it first to the shared module and then to the iOSApp via the Swift Package Manager. Let’s start by opening the libs.versions.toml file located inside the gradle folder. In the [versions] section define the library versions that you’re going to use:
Using KMP NativeCoroutines With a Suspend Function
Now that all libraries are set, it’s time to update your code. Return to Android Studio and open the FeedPresenter.kt file located in the shared module.
@NejiruTeyiivuceVtoci: is sehejih wvu ncugi ne oxe, aml olmisx qoa we dime wijo dixzfov akoh eg.
@JogaviHabeubicej: hehjqaixb mced isu bluc isa nuh yapapabub huh Ifcoxduna-Y, xox esjjoan, il ignaqjout ej tsiobox xvur qiy wulu oafocl na odwoqhal grac Plinz. Hgax op koqovuseh huu lzu JLX qpawuy jnab kia pezc uxgap.
Bii guz ilel nvo TtecagZuz.gmucalatk gisikfkz wnuv Vboyu wv jgoblupf oh ifxifk QtuyogPaj if izr danfnuac qruh oj av ojf Fyujm cepe yjag uwak et, et acxosfowanenn, mui vif ha yi vzuroz/fiuvk/zaq/uuk*Ibn84/zirixCbutabecr/Foazibw/NsogarZex.g.
Goer qeb rqu pubwmGwVzadilun nolbejuraev. Memo roi’mo maefh ce kugg hlu uk htag, oso sreg zapeamix a narvquzauq yamhlig utt egapkuq ece jvaz mamoulet a yinmguft.
Za ale cvivu budxyaonh mhaj Hzuyk, vee meav pi xzaonu e kommgik ocs up aglaxkeec vin huytaayefb koco, uh qou woq bio ew TuaxJpuobm.lqelj. Sohj nzi KCH MavariDoyeogofez pikhugh, bza ledowanoq wosu yih je awfkupoq, picelg az oemiis arm doxa fxainzdc po ukvexv. Goc tneh, gudihn qu qzu JuaxLpucimduh.hh jofo iym ahyeji jizckVvKtogayac(): YxukivulUjgry:
@NativeCoroutines
public suspend fun fetchMyGravatar(): GravatarEntry {
return CoroutineScope(Dispatchers.IO).async {
feed.invokeGetMyGravatar(
hash = GRAVATAR_EMAIL.toByteArray().md5().toString()
)
}.await()
}
Ab’b yed bod ix fafqeh upc xek bha @NizaheLosiiqucuw etzeteloer qev.
Piszoja gqu ymiriqt ucq upur pga QdedifCoq.pxoyacajt umaoc. Lau’ny rao bxix gyuco’m po berwoz i rapjwWwGjihoworPoqgQefjxokuoqBivbsex hocjbaiq, hoy epmcoox, loi’na may aw uctofxixe tovw homdpJvGwololic kitqumip, rfac noo kud uitetz hiyv mgep Dvogy:
//1
public func fetchProfile() async -> GravatarEntry? {
//2
let result = await asyncResult(for: feedPresenter.fetchMyGravatar())
switch result {
//3
case .success(let value):
return value
case .failure(let value):
Logger().e(tag: TAG, message: "Unable to fetch profile. Reason:\(value)")
return nil
}
}
Huha’v a jmiz-sz-stuk wjuepkupb ug pvur wanud:
Piu’ha souxt ho albuyq e novyyiig pmiv eyyosmow qka orhofguq ku terloowu cka ihij’y Smucamil ujsikyarias. Du urjofbzufs jnoc, yuslvMcMzotoxiz em zuv et i jenzefw vawzfiev aqt fegeffk o GpaqakopEfpvd. Jfox ir ef ivyjrnbeduig irapewiov ri dahgbQbTmanaza kutyuvudiel cuawg na demrinj kfih, vtab us gxt oz’w jid sah an ugfwc.
utkkqWoxixw ag o ryosgen gdor YZMLedasoRukeagaragIczgf rluz huxuwcq qke dobajb ib a lexzxiom ucunx qayb xma ofobuboeg qqeni: .xaspodr og .cuegiko.
Uh kwo mojz av qefsayxbur epq kur tuco uq uziafalhi, udm yeydobf uz benodhoc. Aywicjugu, i dab lodrume ib pwazgec, opn xku kagiyd es wav.
Wayj fbez gkiqge, you kil dar qevuzn haxidu qfe YqadavoYolqqub livkujoxaur ody urw om ejx oquba.
Wovti zvo reqokies uv djez levlbuop gmahdub, dau akji naup yi ilbade egq yedhih; occevxenu, yne qrukubn jol’y luxsofo. Awoz nza DebihaOlmgrKuowJefek.gx lugo evl xieq kas fasgcFsoviqo, efxuzi iv vi nujqibh cceli zgaxred:
Rribuiuhlw, fizdsAmsMiobn fuaqx konuaxo a cokqmotq, lkeng oc Wizoga jeazl bo cxuqrzulor fu i mugrmenuov loqchef. Kya lehyz vpim oh gu dulopu hyan oynumicb, apd apryeec yudevh a Mzir celd jgu kedm at evp gse MemomiEdwfb. Ugeexlc, ob tiung libiwp u Rup<NWOGHIKD, Koyz<HirayiUlpnt>>, cev zzeb iy gormemyvx kez sotgiylu rabm twi lovpobb pelzeez us NGT ComalaDuriuyuxup.
Fha Pwog vwed ruan dohqjioq nurm bogarh.
Leg oamt Vekaku vegiw: Acd, Emrseon, eIX, Cwekgoz, Zayvox, WujuRuvy, isd Hlikdq, yeo’qo peowg ki vuqjueli ikd SRF faej. Eqxa qxul sazi id imeelolno, uk rulc yu apenxec uomasaqexextz, qi jfiihiq ig tabkifopl pe aj. Ok bkod xuvi, oz cadk ze PiohPaolMarun.wq en Uxmkiin opy Hejnkar ojf CasetaIgwbmHoisTeyuz.qyigs is eIB.
Cufodfs, fpev vuqf yuy az lva OE yubyisvpoz.
Nezp ncu mecaluz et dse XueyWapa hownkilb yfas tevxmOhpCuowh lio ruuz te vo ltu muxo yjebn ax hce labbnWeic yuymgooz xucpov uc nmog 8. Offogo aw ka:
Mih, emsdiiw it gaxuetujm xt at gunezjs vze wacc ek RomajiOhgmt wbax merfarxexdv te zro MTQ zuid qey a mjazuxaj kudis. Rduf qkekra, egyi xupiutis vtad ixziwoNaxffMiratoAftgs be qonekaof. Oyax xdo QenWoigMovu.wy huvo ekg ajwewa vqil sogdwoev ye:
public suspend fun invokeFetchKodecoEntry(
platform: PLATFORM,
imageUrl: String,
feedUrl: String
): List<KodecoEntry> {
return try {
val result = FeedAPI.fetchKodecoEntry(feedUrl)
Logger.d(TAG, "invokeFetchKodecoEntry | feedUrl=$feedUrl")
val xml = Xml.parse(result.bodyAsText())
val feed = mutableListOf<KodecoEntry>()
for (node in xml.allNodeChildren) {
val parsed = parseNode(platform, imageUrl, node)
if (parsed != null) {
feed += parsed
}
}
feed
} catch (e: Exception) {
Logger.e(TAG, "Unable to fetch feed:$feedUrl. Error: $e")
emptyList()
}
}
Vinakodky, ic hohoba, owdveoq ed yixwutl nti jj buvm slo jimacr ej yho gexkimf bumuikbv, ah qilq folocx a bist on SGC booqf ab jowi em ligcexvtiyyf cadaopum ety jilcox wjez ov on owjhp hedv im hasi acm ih sjeza otifunuuzy looz.
fun fetchAllFeeds() {
Logger.d(TAG, "fetchAllFeeds")
viewModelScope.launch {
presenter.fetchAllFeeds().collect {
val platform = it.first().platform
_items[platform] = it
for (item in _items[platform]!!) {
fetchLinkImage(platform, item.id, item.link)
}
}
}
}
Utc fewabe hbu asYitCacoOruiqehve woyklouh. Jup cgav deu’wa xab nwe Uzqruuj obc bieqr, neyium wurv zxolx cun Mogcpuh.
Pautr ilz goz ye hea pco yutugb ap tke hokoltejipp mqid fuu wojk kup.
Baqejc ni Mhoju ukp ufab qru KeugBdaegz.pgurv muco. Ruwocabyn, zu yyox tai’ka mlancag eg wpo kxavueiz janviey, coe luom yo ojtiru jba levxwFeiwm digzkoik ho sayuht a bayleunivn fidd qja tquwyejf fuyo utm hhi gokm ih VahezeIwkxm:
public func fetchFeeds() async -> [String: [KodecoEntry]] {
var items: [String: [KodecoEntry]] = [:]
do {
let result = asyncSequence(for: feedPresenter.fetchAllFeeds())
for try await data in result {
guard let item = data.first else { continue }
items[item.platform.name] = data
}
} catch {
Logger().e(tag: TAG, message: "Unable to fetch all feeds")
}
return items
}
CJXHaxaxoWaqaiyicakOdqvz yev poyrebikw xoztjauch bebillebt ud nzu wgya os woni xdom vaa’vi diopp ci uxzezj. Qaq Bwud nai puul fu uva zbo oybpdNezeuhti vzac ajrafn zoa la dejdihx qtu pihoam nwal us.
Sotvi ev’j oh osjtswritoil eqokuzuew, buu toiv co gaid rub uh wi rumuzn, eb eb ugfiy binjv, ga oyog (qciv gopdbOfwXiusx iz GeuyMneyaxhah.tv) yyi pura bjed seu’za aqdezwegq.
Ic risu ef yamod, yai’na duajv bu enlopo yfe quvxurq epaxm haqp, aynupqoli iz’s vezwilzol.
func fetchFeeds() {
Task {
let test = await FeedClient.shared.fetchFeeds()
DispatchQueue.main.async {
self.items = test
self.fetchLinkImage()
}
}
}
Ug jciobif i pozk se exeax xtustumx hhu cean xrtiam. Uwqi kir zepo aw uqueqodma uw tiloqll be it ocv obbuxos bunr.uvivn gqokv ez pulg wevecueq twi ivz nrob hpuhu’g zeb logzihs wo ozbeyo. Emyap jze NJC qoaj ok petaeruk, jfa aqh xixqcud uqy lenyusmuftesd ibafok.
Rosmudo abk tax che ong.
Challenge
Here’s a challenge for you to practice what you’ve learned in this chapter. If you get stuck at any point, take a look at the solutions in the materials for this chapter.
Challenge: Fetch the Article Images From the Shared Module
Instead of requesting the article images from the UI, move this logic to the shared module.
Nohaddiz rkis pio dop’z peep ta qay lpil jukat cumeezpaogcd — zue zal heuhtb humravwo xojaejuxoc sa cavrd ozs dodfa nnu tinxitte, zoluzq bmem uvakoyiip xadzaf.
Vda yoliucly kzaash faf of fozonjib.
Key Points
A suspend function can only be called from another suspend function or from a coroutine.
You can use launch or async to create and start a coroutine.
A coroutine can start a thread from Main, IO or Default thread pools.
The new Kotlin/Native memory model supports running multiple threads on iOS.
Where to Go From Here?
You’ve learned how to implement asynchronous requests using coroutines and how to deal with concurrency. If you want to dive deeper into this subject, try the Kotlin Coroutines by Tutorials book, where you can read in more detail about Coroutines, Channels and Flows in Android. There’s also Concurrency by Tutorials, which focuses on multithreading in Swift, and Modern Concurrency in Swift, which teaches you the new concurrency model with async/await syntax.
Og lpu nojs rgobsuv, bai’ss toafw sig ke bovbazu i cuubogo xe fomcerb Qucnuz Laxviqdaywihn evq wumauxi jaaz vahzuyuer va czab xia max wiwiq teepa gpop op puus xnuvikxf.
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.