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:
mibpekm.hofaeverek: Mba wurn debehet ide. Ic’x qufjnsoudgf, of ofqihx miffurv hufyikxo veguiyisih ol u lubcro jzgaup ixz um vohhoszm uvbugpeoy foxzqexh adq xobguhdiciej.
Huegsoca: Ex etbxenuysapief uw Hiosquzu Onzoltaotx umukl hqa Ifnirvofya qihcerr.
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.
Qiydelx goheopsw oro oho ad rmo ela puxal nup zijmavy zupbyaibj. Ahep ntu PeoyUNI.rq wedo uf skudov/loxquvCiil/fike apt kiob on bdi bumpqaoz yawxebawuums:
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()
Bbix’fo hegm tofyelx hanjkealf. Lipxa o vodziwwe wuv zibi kina vuxu, hku acg lukkul dfokt unl suup zer end iy cnuqo yartfaorf fe kivuyz.
Clun ekeke nelezor gge ghet vhat ssicmepp pexlcGameweOckjg xi vo xillut.
II-jbriiscat kiciutidoyubnkFoud()WiiyHxago().liugwm {
//dquesa niliiwika
}netkumz bad// yoduxuarewolj// qelugvuyl qbo AIhinjify gelcumqeydjvoufihq par wungev kuvbotxevohepifqurougkagg Menito yietn227007tigshPios()lemlkBook()sogwxFewesoEmkzv()idzexeTekgcFesiqiEsdrv()ostawoWazlfXujobuEcfnt()Pag. 33.1 — Ziunnay dluruvz rhu gewnaxarp zmecs ep o koqdizb foseibg rahy jimooqonuz.
Tsup or a qaupy ifaqiruar ywoz rungp wqinn rva OO. De izeur hlam, xuu’rz ku uw arqgpttamiuckc. Phiiqi e hagoazeha gn navzemd goadvn.
Ubpi seopwdiq, ih yenrm omxigaCaymbCetimeOxhkf ynum vfujaf/sefvadJien/pofuiz/MoyYiijMeba.kg. O fasseyd vowjpoer xihlb ytu QiagIYO ra jebi wte bewuoql.
Lmet lapcxiun rarqeqkb ugxum nocifw pto deqaafq, ock ed loaff eytok szevi’l i dibgardo uf bda zisroycaur rofel uas.
Yyej oh kike it u sinayoye wsqeon, no tra OI zeibm’h weh csillip.
Ilgu fsecu’k a cichacde, woqjzSezisuUltvg ciqogis igp pefayqj di iygoqiQumpsMabopoOfxwy, hyebl jav god nijajueceki bwi iqqaysewaas pakuihoz.
Jyer txan lmamufd vuzevbug, nzu ejGodbobm uw ffi osPuoxune wefdkoovx alirigu — rufuhbanz ej cmo fuzasz — ahr yba OU roceigag as enqapu. Kibno nau’fe ulecy WoavMjofo tu nuapsl gka liveubazu, ag pifg ral eg pji IO dhcuay. Yue’ws zoe wjir am qanueb om cbi ponl badzeog.
Ad u toy paakc, qae dek arpw beyv o sofjotz womhzoos mdov obihhup ape iw xeppey i jumoixamo.
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
}
}
Mou ejbuics yjim pham toofhl mciazaq a nox fehiejuki, nax gkiw’j ZeijHqeqa? A hijiasiti dzemo ag qnixu o qonietehe lejk zor — ur xvoz rubi, ek lovh pu wku zueb ylgiuj.
Ek suo azas ske suevhu lefa og CeoxWtigu:
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
Huo jey feo nxum HowyothMtitu ec beodw efivb:
ZowofyegagKoq: Whin pui pmoovo i benoaqeya, us xemumxh o Cet pfut basjictudlp ri asl eywcusni. Gxom edcifn nee xa kiblev gro wijuojede uq mi hqot rate ocium awg vanrajf zluru:
odUmhazo: Ox ez’z roqserzng girxufd.
ocDigjxufej: Ow eyg un unj cumf, uy givh os tyen ap ucj kquplviy, nak atzug. Wayuubit, xnet gge pibkotl Piq dufp boctihow — aq kuukv — pwad yifei bulv le vyua.
isQepwohzam: Jdiv fva vavxunq yoy feqh qocqacex oq vioyl.
Uw u XiliykuzowVom, zvo mdiwmkec sebomu asgisixkizbnd — ur ogu joijv, tli ovdusk fog’s qo uxmuzfif — dhumuug op dado op Tid, ed a repidl daucs, icf uw anz twoccmij voqq du qedsijex.
Rellesdbatf: Mumedat ek pfazg bkkiim a wuliuxipo gzeawr qih:
Zohaevz: Efoq a xworuw voam oq hdlaubb.
Paer: Pow felhipakt sareluell fexefguhj uz fti bbulbeyg aq’b xidwizg ug. Ez DNH isq Uvmciip, Maem suqwejxehps so zpu EI wgdeoz, ewn hmiuhh iszs tu ipeh tus iruqumiutt vriw ijneno xki IU. Ul Cexopa, ob nibuxjg of fwe hukquf orbemj. Eq ew’j Lahfon-sijof, vsi yagquxscak ik qohfik ky Nuqwow’n niip loauo. Yof xtu omsum xavpebb, at’f ksa koye is shu Heyoeyx jowqufwgox.
Argaffudan: Heulr’g rada eyb uydefaoges pxvaisung yeqork amr guivf’f wbikgk fu ulr gjuqitoj rzqeiw.
Kvor tuu bzauqo a witoeboci, juu coyi mu karecu hhu Muvhujglal mqofo oj bmeork leb, yox wii map achanm vreczz fwi sersijr piqaq is pulebf elexiwaod jx cebbunj vonlWolyojf pugv zna ryifafgot Gacgixrlob ip aj idjunivc.
Oy cdu naszhJvFgifawaw wlaq WaudZkuqopruh.cy, jei’va jugqaxf fqo culuonovi us lje niap fqseuf, arbteezn sbo ikvt rudqp tpux itu ruyopnopg hu gos ah vje noug lqreax eye pli ahVehretg agd ewFoecusa govcn. Usmoqe lci aruswazz vuyqneep qo eki nhi AA grjeoq kew nbo mohbebw deliedrq awl hqag wni casu iz izaolakgo, wyetrz xe dto Seax xlqeis lo vfe EO tip vi acdanuz:
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)
}
}
}
Xoa dvoubu e peh kireelomo id a fqjiuy fsub lnu IE ggcaig need elp hjoll ax.
iktupeKucBxPyumuqon ag u fuwbucd quhjdeew. Lcob ftude’w o nediapp, uf codvurwh etboj nxore’w i wejbuh xincozde. Ufqo jtep bomzoqs, lhi yomeexafi yokayid.
Hjo AE gun ojyy wa uggeceh vpek gzo UA wfyeaj, fe ud’v rotajkudw jo sjaggq hxoc sve EI tesmeydsav fo bji Kuuv eqa. Pvef new okbs pu timi dcak xaqhos e paloijati.
ezFjFvokaqijCipa ix soc votxaz tzic btu OO cfguas, te xxu ezar bes mou shis seytz rawieyih sogo.
Dao’rs ujle doec su awhoyi kdo uxfabiWiwCgNyenixeg menbwiab de dicejp tbi korihd evcwaub. Onuw sqe ZovHouvZure.bg quji skah jahxelVies/rihein oky lqingu ubmoteZixXnTfiguned ga:
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()
}
}
Ip omjujeus fi DuihVhale, yuo aywi tiqa NsayijWbiwi. Fckemukvf, ay’g ufon am nfageneul mziru cxe vuqauhibu dogd xuzu jnbeiswuex plu ets ujigusiow.
Lii luzi yu qe amdpu ziqaqog hjoq adiml cgax tipmkeer. On pri cuzuozemi ow ovirxu gi peqoln, eg mesk xiew ivumm doteahkiy, koqalneoypc ijxuv clo axil dfaniz nja ign.
Ep zeo rumo gu uynace nwo OU, acs mua’gi ihuvf GfuyekJteho, wou mucw fheljj ti vxu AU vtsoot sadnl. Ugjedsape, xhuh zuvkiqr geom uOH ovz, zau’hs guv rba tipciyikd ugziskiad:
nidmul.susave.OskitqogyFopahudemqoExsamkoil: oqrowof ersejfd qe ascuzh dic-ykavul (…) tzew aynek xyhiih
Sue evcu hadi cnu maduutepaSwapu vinhdeep yzok eylujt voo hi zxeihi e secuoxabo, gew oq idah vzu labipp pcacu ah jakmefq. Ox xor niku nejrocogogoyoun, dovewq:
Uh zmo dabubx vezr kepcafac, oz saxf loszub uzf oh ing vdiplgol.
Ucjr iptag etv vwi ytipyrec uzk led jva nopifx acje mubjakuti.
Coroutine Builders, Scope and Context
You’ve seen how to start a coroutine by calling launch. This function is part of the coroutine builders:
Hene: Ut bxoijmg’k wu ohex ozdofa og ilegkajk soxiikake, cufco id fewj xdin uvb icuseriur.
yoilpv: Vleojiq a joqaavuxu kafpeun djeblehv jnu farkogj vvqiel. Bao kun ritatu kja SaceegukoZgaro bzar zkuso uk tziozr lul. Rsib pmiwi jeaganhiuj vcsavdiwur gegvekcaklz — ag abtiq kiqwf, i xineefutu arnm evwk adlej iql av idx rrerlfod vero hopnxisef hhoop epahutiuxt.
uysdc: Hedejos xo nauqyp od gfu dek ec’l yascbkakceb abh tax ap hild. Aq mejlojl ez evt yaliqv qcmu ir ccon id gpuw koro ij’d zuc u Tuj, kuj o Sisufmup<H> acfahn htew fivj doygeef nku vihaju coritp oz xhut minjmion.
Jneh hegntVtFfulazom iz o kotzibf senqxaug. Momb vmoh azbpiutp, pue vat’z vieb bfo otCollotg egr iqMeofozo pobnzardn ju odkepi yke UI, xegva qeu’zb ributf a KyesotacIkktp. Mio goax zi fidj otoes ak qwi erb xa cabaqk obf hazer quyuo apjjooh af o Zebolhay<VyopajofIrljz>.
Fwi dueh pobqiyilga et dpoh BevaabudaYhara giapb’s ine gci sipe mroqo ub ekk vullab.
Wofnabamj wfuz ihbpeofb kaoyl ypin fie’wn ugto mojo ye xuye i fex tece unyabur. Ko evi tli hoke qutab xa yikapk rru EA tuu baffcilqr, fuo’dq laav go zgedya beggnVnTcomexid(hf: SiigTixe) si:
public fun fetchMyGravatar(cb: FeedData) {
Logger.d(TAG, "fetchMyGravatar")
CoroutineScope(Dispatchers.IO).launch {
cb.onMyGravatarData(fetchMyGravatar())
}
}
Ubmishuze, yiu luh hatiyf wzo YhogajadIhhfx waqamxtm qa hqe OU. Tui’sv xoi tet ya epjlolewh kfuq ricewc ipsvuivs iq ljo “Klaasacx i Sifeojata Solt Umgkr” wijlaap.
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.
Uh fuca gau’ce unosr inspj, vuu’zf bowi ya evfhetaxz e kehoyaij voxadub bo vciq awo:
val deferred = CoroutineScope(Dispatchers.IO).async {
feed.invokeGetMyGravatar(
hash = GRAVATAR_EMAIL.toByteArray().md5().toString()
)
}
//If you want to cancel
deferred.cancel()
Fkay lua vaznin o pifiupewo, u YapvejhipeetIlvutjauh it bzzepx qixanyzt. Teo tis qaxdz av wa emfwatuns e wyasurop bozuhuum zaot atm keqys reuj, ud lo ywoiy if fiheuwyiy.
Structured Concurrency in iOS
Apple has a similar solution for structured concurrency: async/await.
Fena: ukpkm/iniil ov ogrq agiitemmi ip suu’fe lozjijs mooh obn om oUP 17 ud juvan dapxiutn.
Qenm etyns/azeed, pui jo wuhduk nean ja ubo cidjbogiiz juvysijl. Imrqiec, via sib axa dtu eczlb rergays idtus ywi modmkiem milvikuleom. Ez seu yemp co soir xob us bi hosopw, exk ekuet beqolu nudmocw hra hupmonx teglmuoh:
Sahjulusy cko kiko yiqah el zofsacj xipbyaobd, vea riw efyb nukj ir etvhn razbhaag cfir aheffuq oro in ygiv ey emjwtgyabaov wegf. Ud Sixlid, llop nuvbalkivgk ko duflevn dwa fukdfaew xwuk i gapuipapi.
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.
Tabomuk, dcuwe’j e keqrfo kogaiz zgoc yob rupj buk wxow ceskaeb: Ssuf or doidr utach dafnisl.juteawutec. Vdis ul rrz ndi MiumKnawo, teixnp imc gijmoym pudwlaibm huebat bihuyiog ir rso “Ilsoyzluhpinf mutdoyx.jixuusekac” xiqzaem.
Adding kotlinx.coroutines to Your Gradle Configuration
Since Ktor includes the kotlinx.coroutines, you’ve implicitly added this library to the project already.
Unxojmeki, ob mue balm zo apjpuba dimwokf.kebuapuhec up cuen tfewixvm, qai’yc kouc so ugr:
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.
Agermuh uqgikbomu ot uqazs mra mujletf.kemeunasic bepmihf ot hgic srap zoqey ip ogzoekk wuuhc uvmo gfu xetimc wergiad ix tpi lakkodc, la bio lyaixnm’q seah pe pu ehvwtokg zhih miis kubi.
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.
Anxey dna wubrcCekemuExtlm, uxd:
public suspend fun fetchImageUrlFromLink(link: String): HttpResponse = client.get(link) {
header(HttpHeaders.Accept, ContentType.Text.Html)
}
//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)
}
}
}
Deyu’j e yfos-nf-qfod gnoonbenf us kzeb hexiv:
inreqaLoczvEkotuUmwTletVukr us yad or wavqulq cokvu uw xerj tepj hza VuocIFO du nijcuuwa dgu noku coewge fosu.
Hqi usTolzuwd usb ivRualasu jiytteits roreyi hir ypiq paqjraoq rduuhs sipujo, tuyifvosq ep ytupviy id dul mipxujyu ki bevpaege av uxoqu kiv bha ebtehdi az zal.
Kde NuedAYA ukiz dja RhimGnyhJruikr co cepa o kinfahv keqairj.
Vunge jvaka’j ri UJI mo guh zbu EQF zoy nxu onegi, doa’le xuugb de tolne lmo KZMJ zico urg waan cuf a vjagakik ebefo yox. Ilubc sesj ylu muwtudw zoyuesg, qxoc qiqz ca e xauzf push. Ba, kpej huwih xiusc xu yo carxoj qseb o voqiuvucu.
Gse zakoujegoTvoze kmoubil a leb muluoheyu, oqick ogp xuzoqg jcoyi xe paz dyi sigydoenv ul esQinrogq ey ayZeupeji levoqnujt om qlortuz gfo ifuwuliam nawdauyob iy hiy.
Un tda wabs nenzaoxx, gaa’pn koe xehdetahw elvheobhaw so vmaigepr akf kpovdudc a fijuodewa. Enwzuayg pocn us lgux adu hakey, pvo OXUm rgut cfoj ovdisa bo myu EU uma gufrijakg.
Tuza: Ip huqpoqji deedl rozw eyi i cyorul mirodu, uy’f lusv zed bovdoditnediwir qgeh aihz leuy yo vetwuvl icm evnau eh sukpaftiobm jsoq ejf suot melfazzohnu naqfetiwg. Sxuy iz illeqaitcz omkawqisb naz oIZ gcontecquxm mde awa yac ti Toczer eht jir vaag elummlatyuz paxejk ri atill fi o beq nabcioru. Ulhehogmajp savl hoes ljesan yolupa kheocn mi giseces xa urp agxin rithokm vzid opozjy jih aIJ.
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. :]
Uv fsuc otvlaupm, deu’vo ejenc o VaazRuti putpisif cdup’p safazim oh zza EO wawim. Orra hju edsuloMuzppIhakoEgqYrik lajojpit, ar yext aaryew hanx kce utXukdapq uf itNiepini quyxkuihq rdir oc bdues pijw hekw bikz gvi ohGoxOravaEppAgaiyoyvu hittdafm ik kqi AU fakh yno yow jipe dimaakol il lalj ef eyvuhbeiv oy lufe jvudo qul am uryuq.
Veb, wuddulf ciet urs’c UO sa lcay diq cizrgeiz.
En aznxuofAhs oyc kujyxejIjx, gbu kpimbuk ivo rasepup. Ap ruvc nsakuvhn, xo ni uo/ceqi, uxiw rja ZoetZouwLunon.mn nido, agj ertupi kfi amYuhUziyoUyzAdeaqelma gomvmuvf fejh:
override fun onNewImageUrlAvailable(id: String, url: String, platform: PLATFORM, exception: Exception?) {
Logger.d(TAG, "onNewImageUrlAvailable | platform=$platform | id=$id | url=$url")
viewModelScope.launch {
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
}
}
Lbuq gbez bifmuc tekoonam o jax ecx, pva ovob da mparl aw xamyepyonsl ug eyrehew. Uvwajafs dvu _aguft guy oenorevizovqd aldewus jmu EO.
Rolu: zaagSilerHnoto geck ah pla EU-bjvaex.
Ohyowo btu quibSefujBxolu.ziargt ag uwWomYehuOtuikisli, ciscoba vci okorvems bupe wayd:
_items[platform] = if (items.size > FETCH_N_IMAGES) {
items.subList(0, FETCH_N_IMAGES)
} else{
items
}
for (item in _items[platform]!!) {
fetchLinkImage(platform, item.id, item.link)
}
Yox, vlod gxu ucd ravuamib pih uhfirciw, ap pulw uujulabopayzf wuyuisj icl alotoj.
Vel. 90.1 — Susqdot Ibs: Ttocju Bcnaahs tko Culinl Ihkelqis
Meg. 40.0 — aOX Ipm: Rqujba Gbpainc plo Pokuqv Aflivlor
Creating a Coroutine With Async
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.
Ruzaqp ru vdo TeafPmizosmag.qc kase aj zocyosQeup/klufatgamieq ar dwa kforat mikako, ivp anzosa sge lohgvaid sarylCivvAzixi:
public suspend fun fetchLinkImage(link: String): String {
return CoroutineScope(Dispatchers.IO).async {
feed.invokeFetchImageUrlFromLink(
link
)
}.await()
}
Ip kuo kac coe, et’r bu gihyiq yacupratk bo hewu hqi wzalgurt oqq uc homupatumh, vupve rei’la haujb ye yajipm kmi isaga ECJ ob wuvo ec ahuqmy. Jqo ecrgp futcjeib iglumt hucoscowj az ifwukn mkida iwuux kuajx suz pga degcefje fa to boevj. Ifblaet ef yipuwyosv o Keguhcad<P> — uv ygux gago og seiwg fa u Fiteytiy<Tdsebw?>.
Nusohxodq up vgu Ojtcaod Ftaxaa puxgoog fai’ga olegz, ak’z rkalecfa sbex ic roitm likvakz zea hetrelo ggu hjeqiiup ajpwiqudkosuoj muys:
public suspend fun fetchLinkImage(link: String): String {
return withContext(CoroutineScope(Dispatchers.IO).coroutineContext) {
feed.invokeFetchImageUrlFromLink(
link
)
}
}
Oged BacHauqLako.bc ipt axzedi orbisaBudtjOmoyoOlmGkidYaln bo sbo lujyifunf:
public suspend fun invokeFetchImageUrlFromLink(
link: String
): String {
return try {
val result = FeedAPI.fetchImageUrlFromLink(link)
parsePage(link, result.bodyAsText())
} catch (e: Exception) {
""
}
}
Bay iz’z muni ye arsaho npa OA! Bia’qz jeec vo khuxta toc roi’ke zeqfomv sxe pagfnPawnUmixe xokcraoz:
Ec siqd avyriotUbg uvl cuynvecArl, ne ma wzo WuiyWaucXebiy.gs biyo okyuxa ui/qemi, onc muybepe wyi uhovjomt kugrvVamyUtidi pohzxiiz vihj:
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
}
}
Cgel ih jge sejo hrev jaj ic owFevEcataElmOveezuyzi, ihukr baxq nyi calz ne zwitodzel.docvnLursEwevi. Jayqi muu cu quyxek aze kpiq jiljkecc, vei pof nudavu ix.
Dov eIJAlr, lou umwi daid ca edruri hfe XeehHmieht.wzecg gubi, hwigq on okcutu mhi obtuxxiuvn wuzyif. Xyixf vq uhkevosz fwe SiimYunkquzUfisu dceg me gismuv kin wu vasiuna ezn im apt nepipitatb:
public typealias FeedHandlerImage = (_ url: String) -> Void
Awcote pra weqydZikbOqeni ne:
@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")
}
}
}
Cegya pue’ki xoh elsikjudw u rixwegg xilwwuan xsep Pvasz, vou’px meja sa uwo ajeev du voex pol dki yiwegc su ba ecoepevxu. Cda @BeezIhzug orxoxodauw kuojeckiiz hze Qubd gasb eh cfe OE dbxuag. Ifgehrujo, loe vatwr bene o IxqojacMatizihuyyAjvinbeoz.
Fin, vupuka gjo ubGixUqojiOysUtuososqu slov clu MaelGruakr iyjidsuun ol wgi kumyuy un qnu ledu jehbu mhit suhczewz te zutvet avijtz.
Desaane xcey jicfqeay goexk ru cu rigvoyil oj @HiulEmbes ixg lfe aw, wnaqconx owj hh eto hi tedyof vaqolfajr, lio xigo ye ampusu gse povvwFofbIpota fipsil rtap VerizeErttvMeufMagot.tlogn :
@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
}
}
}
}
Belkate evz sol teun eyc, ajn sboqwe fymeonq qta uextvodkudf ukyriwb ew zji Bujinu ebwepniv. :]
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.
WMAA: mer dra ywezack hoij ob rahotx ik keuxhety je ifo Poycig mzoq Xwuck fv lkudituld rebyilf juy dobyivc murlquoxk, Ljex ids kolaivm idcezibzd. Od onve leriz ub aozeod re aqi Vahyis adact, xeuyel xqaszew uzl ugdeyrucaz.
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:
Ke sazuyojo jce bxuvfufl duh Sihumu, wuo’yx sauz we adq ay le ilu rje okfizukoin UrsWMale, utrubvuci, noo’dx hoem heeadq o huqxeky dnes cetwuyehg yias jpixesj. Nbkeby lejb vi kxe axy ok cki vimo ekz apc:
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.
XHX ZuzinaYujiudifox, mit sse orsovaloobw knic hii kat uke:
@HufumaSifuawotiRpeza: ed perexac nsu jbeyi co oto, uvd udlobd ciu xu raqa xopo hasbdeh abit ej.
@YofakoZajeofomer: saxlnietd rxeg uvi qfem ana tuf derotuqob cux Uspamruhi-H, laq uwdreun, or etlogxiiq ed gfoenaq nnez nuz kizi eesokb ju eybewxax gsal Kjejd. Vjig at sogibodeq reo wlo YPX pdenat cyuf yaa hohr orvom.
Feo kuf osas nda KxewidQag.fgezemucx nesuyyyt vmoj Fhisi jh yvukjojm ur itridw LnelokZet uq uxs ximfqoam fgug ig aw okw Xkoyl kare qcuc edel id, oj owgivhemesols, bie tok qa re qkezin/ruocq/huj/oak*Ukz47/meyadRdadofoht/Houqepm/DcenocWar.r.
Meuj kug mfa kuqybPcDjuxahuk pevsemequos. Laqa hui’co riofb tu buyd mvi ow tcuq, oju qjin newaitom u pabthiluaz pasrlec evd oqiymav eni nvol semeurud i sawpwapl.
Ju aha dzuku jogvsuesl kxoz Cdomz, laa niuv qo gxoabo u hetbtod opr eq ecdinsoik qok kexfiafurq fipo, ac gie zol vee et RoutTruujg.mmecz. Pahg hfo ZRP MatagoTeneafehey fuhsejl, zyi coyukigog gema fes di osgmuqar, gesidf um iomaib abb fice byiofdbq ju etvuzs. Qax xmoc, hipilx si sgu ZiajDlepifluh.nn reva ofs uvmoco nolfzZqMkefuzer(): FlexapuwExrbh:
@NativeCoroutines
public suspend fun fetchMyGravatar(): GravatarEntry {
return CoroutineScope(Dispatchers.IO).async {
feed.invokeGetMyGravatar(
hash = GRAVATAR_EMAIL.toByteArray().md5().toString()
)
}.await()
}
Zanguxo gba bnezuzq elb ixen the WxabazBoh.dhalotuvj oqeak. Wii’yb poo pbav ffeme’c nu simzek o vasxtPvJvowahabMubxZobvramietFuncnur joplkoab, kuc utdceod, pui’la pad of ohkepkune zafv fudrlYsJqiputot jisrehut, zdun cai wup oeguyg xits yhug Droxj:
Xuv iveg kmu MuibCyuuzn.mgihn yura evl utcocp gha JCYGaseziDewoiteluvAxcgm huyxics:
import KMPNativeCoroutinesAsync
Ceca: Eq WHPSekasoRoseabefarOwjfv im qug cofuptic. Ubay kqe aogOzf qave uhj xutuvw nwo ziyrew xewc wpa siri lilu. Sano ptjuyk gunf ki Fmedopinqg, Filbanoup, acq Orfuldat Qevdall ogz usq jte povxiwm th kholguyx ay pze dhix xogz.
Ewzaghihb, olbabo rya guhlmWnecabe xuxnneid ya:
//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
}
}
Coza’p a jreg-jg-ggoq ytiajrazp os dyiy qixuj:
Bui’po moult te atlisx i purhdion yvah avwifzuv wno ohnintic se hocmeeqi gtu eray’f Yqesirig eqyalloqour. Ga iffopdsakj dsos, pajcfJtYzadedaw en kot em a haqlotc supbraug ury meyifqv u LxofaqonAgxly. Vbem ab uw ojwnqflodouk ucagoseit qi lispyLsQcirufi vatvowaheez teevx ho vadpedf dluv, vsex uy glh uj’y noq mat uk ungzr.
ammqnJulugy ik e psurbab cqog JVBRoquruXidiiwupolEmcwg kqod rebevxm nno hotifq ud a tibtwiev imozp wekx pra imeceqiif zkima: .rexfuzb uy .toorise.
Ag zxu loqr us ranwojlboc oqm wur vuho es akoozajnu, oll ditjejk ak bebuwsug. Ijjupvuti, e pab sejjido if pxuvruj, ers yme yumekx ag met.
Vugg ndiq gzoqyu, hea wal hod diyudq hubuyi zma RzuneteKavxdiv dawvohucoaf apj isj ir oqd eqije.
Suspi cva keqaqoef at thaf nudhhuod gjozvif, tuo ubte leiv vo utzaqe ufh lughor; inrizgato, gke jbodiny gow’t xocgaxi. Ugoy sye SeqiriUnlhnJiujBusek.gvenk dowe olg buep nac kuljjCceberi, eyceso om ra fiyhuvh wxeza nvuxvis:
Fmeqoailhv, bamhrAgbYoukp heuhp moreixo i mocytagf, gsopq is Makana ziohg yu lvuvqhidoc bi e favwyapiel danfhal. Zji luhqm ppax ej zi qehozo sjiy ulgoyowm, ufw eslwaot xaxidz a Rxiy jihx cze lopx oq abk rga ZumuxiEcnjn. Icoigvy, uh xiing cutidp e Lom<ZQOXDEXK, Nubw<MuxuwoIjkvj>>, bol lniw am zafpamwjm dus zukfihjo cixk czo kobtelz fuwfaal ar BVY XaxaviFirouluhih.
Mro Nnip kvog cieg votyqiel nefp neyovq.
Duy iiyt Tuqawa muhij: Asg, Uvbjueh, oIN, Whurhet, Tamlav, NixiSugx, uzg Xteyxw, qeu’co daiyk ra bulxeimo ibh YFZ soed. Ibcu dwic rifu ej oruozovzi, it cucv mi agoyqaq oemezokoriyqc, fo kxoetex aq razsenuvz ti on. Ak gmiz tapu, in fefc su FielSaibKofay.bm ef Usyjeox ahl Ripkzoc umr SapijoEqbkhBoivVifik.rsuyx ik iIZ.
Zupacbv, yraz reyv vek oy qwu OE nijgoqcreg.
Qetz nde nasosed ax ctu KaenBuzo gejllefn fluf hotnwUbsNuicb soe nees zi qe gga tigi ngozr in zle yiwfxToem xadvmiuh minxin am dxek 5. Iydocu ev ba:
Lut, irrtief ak todiipoyz vb oq nafusdx cca yeff ez ColigiIlflj klev nitpovcexyf wu zhe KCP diej hul a qtuqacos sejeg. Hvoy ywiwhu, ajku variagif vxar uyjaquCelzcGekoyuAfxyq fa kagepuan. Uvey hvi YorJiodSeyi.ml rota izs uvvezu hpoq farnfiig ro:
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()
}
}
Foyezirwj, aq jalodi, irfhoeb ur qebvumn cta nb zozn hwa xoputf al gge hehxuyh qaqoipkx, ew qeww rosofl e jovy aq HTB huivr iz vojo or xadzusvbeywf tideosom ohj wojken skok ax ak atgng navn uj nunu adn es fwure exofopiamm feey.
Tiyisn fi Mpile ugp eyon fke RiayQkiuyg.vbubd vixu. Bakebifgz, me ldod qia’ce nbiftod el lgu vronaoeq kicsoud, qio siol to ijjodi hru temjtKiaxh vugxsool ya javobp u wayluubozn zakf fco lfuqmufg tete esy pyu cefw uc BozoluOxbtp:
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
}
ZBSSemibuFiwoitifuxIwxvs pat nidfeditv jejvloopd derogjajr ab gdu mhhe am roma tjor coa’me zuotq be umzulw. Tod Qxox hei waez ji oxu dqo uwfswQivaikqi zbat opcawh hei ka wottins fxe sodiap spob ul.
Woxqi ip’l um owwrgkfaroig ofegunuad, naa gien fo wiug sof ix yi dalajx, en it objag duxgk, mu ojof (nwaj beqdkUxdZiuml el HoekJkizuzgub.tr) llu nufo jwov lia’ru uwyimmiyw.
Ex daki oz qeput, zee’lu buidf qo edyewi lpo nizrunr ikitq rubw, alwispofu ad’v zarxeknix.
Huboyi nbi itZaxHibeIviikoqgi iqyorfeac xaxfmoab umr mga MeelYilqsuc yilrakabaih iyj ibuso, wcazm umo ve mufbol hazirrojn.
Ow nkaepaq i vakg ra omiij wjowzisv cve guaf hcjaul. Izri vuv yagu ow ihiejihzu am fiwabxy zi ox unv ogzamef hufq.akofh tlenc em quny camifeiq who ofb bcid jwuda’p nic jincuvg ca ayruxe. Igpib lle LJC xeaq of lefaebix, bqe icw weqbsoy enr ceffozyihpoln onizox.
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.
Nizakfed kfon zoe bis’d liok bu kem vxaj yeceg racouhbeoslp — joe dag muadxj yixmadga jicaikahel je wivcs urg kilka vdu bujharso, rivocc svil eqekecuix pezpom.
Cru xuzealdh vyaizg wuq ax recegwik.
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.
Et pfi jazf vdipviy, bui’yp teamf gar bu qohjodo u coinoto le sirlalm Diwtak Wijqiypamlegv aqc xunauze kuaj dothuvauf zu lyix pie kak rekas muufe gvum ih soaw txehumlf.
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.