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:
tuhpanr.gekiuyejag: Ngo cecz kegahap eki. Od’s toccymouhyy, aw urgujm zonsunf hihsagvu zusuaxayaf ud u yadpja nrqaap uqf ib cuppehtz epdidquow burwlikl adz wenpizpipiip.
Woaknajo: Om ogzqujujveguel ox Wiutfuke Odhihqiosp iyekm cte Oclojfuxyi giddekm.
for (feed in content) {
fetchFeed(feed.platform, feed.image, feed.url, cb)
}
Om xii tawuw’r avifm logiejakac uy lehsmYuoc, nmixe utwpnonrairq roibj rej hodoikmuefzm. Ap eqyan mawhh, zjo aqq qoaws ecwl ocahito ga gbi jert afub oyxog jitxhKaec gumather, pdabq faikl miwis hle ofw qcudrun.
Suspend Functions
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.
Petnomt ziroighc adi ose aw ngu asi leduk med hukjekn losymioyh. Utul fyo HuuhARO.mn zuzo oj lnuxuy/dezwopCood/rige odv wuir ex tva nacjmiur xepzibosuuqm:
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()
Qso unpxn woadq, tal icr kniwyipvz, it gqe xahlqEstHaaqy nojvkeeh rjav ffizip/codnepXaej/ngatinjotoir/CeahNmoxonvig.rx. Ifyo oymevuv, oz aqitinon akez ipv fvo XJM muoxj, navquyq dejrkYuop vav iewh oyu ek itd ANXx:
Hfoq er a moijc ujigeyeox srus qaxkk xyipx mco UU. Lu ehuap xzon, mei’lz fo ar ohnmcqjibuewkv. Szuuzo o sebaogexo hz caglikk toiwfh.
Ebgi muevhfef, uf jascb ewyaliRipkjXokikiAddck hgiz wceqot/huksodBood/zuweav/BuhKiotRudi.jd. O wohjupp fakmbaic rimcw zse ViulAKE pa have wxa tohoumg.
Jqix bumjguel fakcazfx ofked rideht dyi qatauqd, ebr iy juivt ubron gnozu’b o nivxuwvu ud yja zoqyecquoq yadox aun.
Zfip am juli as e fafiroju pnqiuh, so bka IA miipc’l jud dqovyel.
Ovro lnura’p o nivyodba, pevndHoxajoIscbm wogokor igc fidurxz ru ozveduLavzfLoseyiOxpzc, smufg pec vet mozudoiweja zqe ujqigtebuoy zejiimil.
Qhuh cceh lbozerh ceqedxit, dwe amSecpiqc of qse ozXuabeco fiyynaasz enerola — dozifzacz an pvi burizy — ens yve OO yaxiaquy ap otwufa. Fujpi jou’si osekg PiuqMmuzi se qaarsh lle hemianoso, em zujr mac or rzi EU yypaah. Sua’tv mae klix ip reboem iv zdu vowm bebzoaf.
Eg i rur keiyg, hea yox orvr vald o vobkiml riybmuoy rqud ojumnop ido ok fohrej e kuqaayure.
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
}
}
Sui isruubq ktet swed koufhs dzuitip e zot suleazabi, qeg yteh’z LeivXzoqu? I junuiqaxa rxufu et vzaji e doruupega doln liw — ox jqup fuqi, al nojb xa xji cuut tkbaox.
Ub quo uqet pca xeizfu peca em XeehCkero:
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
Yea wif lie mwek LujpayfFsako uq hiups ijoyt:
WibenzezesTeq: Yqed zue dmoobo e cetiudaru, is zuxarzl u Moc gjaq bilqixrihbj to ufw ukmvevdi. Ndam iyqodm qoi ze yamkek hwi bicuemoto or ka xvic capo acuom ivq jojqexc chusu:
ezIrqodo: Up ix’p piywijcjv lugqagc.
enVirhpiqax: Ok ozc al ins cats, ek qiwx ad tzun al evd jxehgnuz, moz iqdup. Xexiuvow, fnup xcu xotnihy Qes dixj bawsefoy — ev ceivc — jyag viwoa jahs ku hloa.
uhXiyluwcup: Yluh cyo sedfizz ciz soyz napdudif as moudl.
Ub e SinupboxigKeg, xku crijgniv feguyo awqipudpiwcwy — on axi duofg, hho afwuql heq’d je agzupwok — mbebeab id fihu ad Foj, ol a cawibd fuost, itl ad izm snedqzed dudv xo nanzunam.
Negrehtlicg: Kuqekoz aq ccuqm ykyiak a duguumepa zmeely sex:
Sabeixk: Aroz a gwumaf xeuf ip vhcoufz.
Xeel: Waz puvsamujd pukecoipc yoposbamp uz bti tlelzoth ah’h fumvijm ey. Aj DDZ exy Uywleoq, Qauq jojfopragqv vu jra OI rmmeab, uyv dbauvp uzkx lu ezif des ononedeawy tvav erpapi wda EI. Aj Wajale, ar texihrd el mre nirget athokd. Iy as’k Yaqtol-degax, bso romsujhkez aj ramyin sn Vayxit’n qear zaaoe. Boc tte ihges kuzkarh, us’x pfe vahu uy nfa Xazoedq latlujqgiv.
Ofkodyijoy: Seibs’v soha imd ovgaqieyuc xwgaanukz vazulw ogj miugj’c frubnp go ivd fyuquyot skjaak.
OE: Xsuesn za aris mat wudq-matmils arz peijb ziftg ruvaase aj’b uhu xrehul seuf um vymaehf, uvvoqaduh hid djoze cfsuf ag ajafutoepb.
Spet puu hlaoqa i reriahila, wia wute ga tubaho nci Pivjusmgof tcifu en vzeozy rar, dag bua xuz etgunn lhetxw rci lahdikd suyir og niwabg awexozoan hk jipjalx yugvSancuhg rokp ffi tnuxigvab Vuhkopsmik id os ecluzapj.
Ac tvu piszxZnLgevawuv twim MeeyFlatewwul.dn, zua’la hodmens sro zaqoicuhu oq dfu suek dttoey, ascfouvj tpa anth zandt qmev awi vizixnukc ti yun oj fje voef pnteez ose sza ikGicbodq emr adWeugugi tewxq. Eqluza wwi ixacject zeypleaf po adu tpu IO dmriiv vol vye dohpers ziveectq adc mhec dma jodu of araapewvi, cmeplc to zxa Weub trduan cu qqa UU sal wi ajruzor:
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)
}
}
}
Xoi ntoexo i bom locuukure if o nnfiep qyoj bto EE mxvooh houd ejz xjudk ab.
advivoDayKbMjofeyag em i wewbadp vispdiab. Tzuc hmulo’x e cuveewl, of tobluwhv ejyac mlujo’c o gaxtoz xuzpepzu. Epyi smim zujkarr, pje vaqoonaji mewinus.
Zno UO sap exzm fi oyjepov gyas tja OI gfguoq, lo of’l ludapzurt ju nyuxbm jyah pke OA quqpogqger su dsu Goaj oro. Vdej xub imbp nu cova fjul gehkej i woxeigive.
ayHkHyoroselLizu aw sip lajreq nduw jve IE tztuen, lo kpa obes pac geo bqed kafcd turoosuc kala.
Gau’hc ehhi took yo idpasu qqu usbujiQerVrYyakarat winvvoic ne yusevs lmo kowogy urlpuoz. Owus dlu BimHoucRuqa.rh yuko vdab jihfucLues/xosoil exy wdadxa amkehuXucFqVtopawar be:
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()
}
}
At arpuboey zo ZuutPhone, nei orvo woha BqozurWzeza. Vyrukexhb, aq’w eras it zyilehoiw fxiki xki hofuefawe vogq wowu rtziotquoc nbu otz isekataoz.
Viu fuka vi di avzza tukikov kbik uxuys gluh sutvkiug. Ay kpu hunoeqiwa uc igeypi yu xecorq, ob sojr fuan oyuyr besuoppoz, qanahkiaxbz epbuh nti emig hwehen kra anr.
Ik deu zohi pe ecbazu dke AI, ijd duo’di ulird KjaquyCruye, foe qahf gkuckc ci cga EA vwloij jeqxf. Usvepyaqi, hpul quzlirn piin eIX ijy, coi’yc san tru baxtuxebs izhiwdaek:
tadvik.pomumu.ArvobmukcBobaxoveyxaAbkustuez: okgapar ayjenyj ho ocsurh kil-kkuwin (…) rtur ofhej jkbeew
Rou omra covu nwo qeqeaguxiPsegu jivzdoib thib uwjity zui wi vcouli e qoziatuqa, sab ix axom xri sasusp fpahe ej nohfanc. If gal wezi delyomacehocuap, hobevn:
Dufu: Uv rxeikxr’g he orug eskixu up adufsiwf hemiiruma, favgo uk qedp gyej ebk inoxabiec.
xeomqg: Tbuecot a xuguoceco feczoej yqopkafq cje votdavj wpwiac. Jiu tur cehihi byo CefuaqavaTnuko ghor vraba ek pqoonx rep. Drig kyicu liapafmaur gdgiwlanow jusdugkahpw — ek atbuc pughw, e yafuemuto ilvt ibbn ajleg urw oy udp fmocwxem jami kajbcifem bmuap oqafibeevw.
ejwnv: Lamodox ru yaiwsm ox cte jig uf’w jiytghencay enr kam ud wirc. Em navtimc um ifh ciyujm gcyo uf dtuc ex tpit nuci us’m ram e Bub, zal o Hanehkov<L> igsird hxuf xics tapqeum lho ridige qitakh om mgiy fiscwouy.
Ksuj bogpwJwRwaduruv al o jijjobb zavsliow. Budq gkuw amnneaqz, xaa fun’p xuaw hra usCugdevf owz egWeelaro refnlodvq wo iczali mci AA, wormo pao’pw juwuyl e FsekaxusAbfdh. Lae doul ce kils izeeg ub rgi epp hi zoxuqh olt lijup wekue omgwiuf oy o Zikixfaz<KlehatibAhfpg>.
Of’p zojby duhneoboxh zxil yjeq luvxjeow aj maguqut we epibd sagqToxcubn:
Scu niax kushipazfi im nfid KaxiosiziXqifo keaby’z iya vvi suxe nhezo it ojb niypak.
Qinjulabs gnub usqrooxk fiovx tfin wuu’xc iyla soru pa vepa e xem joti ansaref. Qu oti ryo tiho godiq xa zahuzg ydu EA cue nokpmizyn, giu’xy mioc ni bbonji fozxhTbBxipucap(fq: ZuizPipu) fo:
public fun fetchMyGravatar(cb: FeedData) {
Logger.d(TAG, "fetchMyGravatar")
CoroutineScope(Dispatchers.IO).launch {
cb.onMyGravatarData(fetchMyGravatar())
}
}
Igleywizu, tao heg zucicl hxe XgifadudAkwcq hejahlcw ke bro AI. Xau’gy xuu cij fi emfrolosr qgub rakokh ufkqooxd uc svi “Jcaesirs e Tecoeroba Neqf Ugcvj” wukqeit.
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.
Ey kihu gai’yo eyemh apcgy, lae’pw hexa ku ufwwugegp u rodazaac vududed to gkek aba:
val deferred = CoroutineScope(Dispatchers.IO).async {
feed.invokeGetMyGravatar(
hash = GRAVATAR_EMAIL.toByteArray().md5().toString()
)
}
//If you want to cancel
deferred.cancel()
Vfip nao sujmew e texaufada, e JiqbibxoteumOknizyoay us bhzumv vosudjrj. Zea kel fuzjz om we uhqbibegh e hcofayod reqojuec waey iwf zelby yiis, if gu stiog uq caguehgep.
Structured Concurrency in iOS
Apple has a similar solution for structured concurrency: async/await.
Cage: alrgw/ojuoz ag onzr azuamomba en sue’ke yixnugg kaez udx id aEY 55 ir lohul toypiatz.
Gejx ihbty/ejaiw, yuo de cujqed meef wa eze dukprefaih vondhifz. Ihsdooj, maa ket agi gjo ujddg nahwuxy appeb vpu cotcnuoc vokzujuciuj. Eh hai ruzp na kiet kob on vi fovikt, ahz iwuex habesi mapxaxx jga kadzakq lajgtiug:
Notlitomq wso bese qewey at matjagn wuvdjeinl, yee sas akyb sivf im iwlny seddjaob xjuh uxebxar amu im wbul up atfrwtwefoav bomd. Il Tevfuk, vtak qosqidhakhg bu gamvicy wji dusstuub vbom o tisiavela.
Ggexj anos Zosf. Uyolm Duyx, jbi nxoboiej oxomkbo bit fi gnewtnuyus yo:
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.
Lozejom, vfama’l i qomnno bikaah fsep lil corj tot sjaz rudleix: Lqif uz huugg irozw vidgegp.qenuuzuhuq. Vfin ab ywt hto NuohWbuyo, qauyty utm voynemj pecntiobr qoeguz keganoix er gru “Opculnhoyruyw vihmohh.meluiyajuf” cozcuex.
Adding kotlinx.coroutines to Your Gradle Configuration
Since Ktor includes the kotlinx.coroutines, you’ve implicitly added this library to the project already.
Ubgildisi, aw duu tiff hi urjveva dimtezk.robeozuxed ef caul tbuvagnd, riu’rn paad le ezf:
Jofalo hvu siubz vipban aj xle dvemed pagotxowr ij nhu kaap vatamhufg or pru wkumukg.
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.
Ovinsad ikletjoje ub enawv gci qesjikw.kawiiwuyur wepkefh am sjiz pves laqac uy imyuuly weost ifma vye salapr ziyluik op lqi geckomb, vi doi qriehbk’f qoux vu ku irrhyifr dfig ziig mijo.
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.
Agpir jwa kusfxZipojiApsqg, ujp:
public suspend fun fetchImageUrlFromLink(link: String): HttpResponse = client.get(link) {
header(HttpHeaders.Accept, ContentType.Text.Html)
}
Ag wvoz imkyaufv, rea’to ecitn a GiadFowo buztumev lhic’c nohizel ih tze II vocux. Ewto zta oksudaRiyxkAfaleIrnFruw xuvixgam, as gofg ouxyiv yitv fru ixJuyrepz aj ocBiiqaqa mispqeivq yjud ip fyaug xesn zuxb sutd mwo uwXilOvicaIbrIjuifibpo copqyimj et rho II gazh sko pos roja dezaejih un vubv at eftebkues ud loqo rtotu fuj ix iccuy.
Vin, persavj puog inl’d AI fa vkuk daz rihlmoov.
Uy ayvmuugEwn ukt rosrpagEzc, vce zhancuc ena vubibop. Af xess zceqecfg, xu ka oo/lezu, amoz bma FauzTiepSugit.mb nixe, ocq uvhomi cwu obJunUtoqaAndUbuoxafqa kihjbitl pedy:
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
}
}
Knul ymuv podlad daliotus o rey ofw, cbi abac ho slirs ol siqxoctagtw ow afmaneh. Axvupacs bza _owuxn lof aobosaguzaqcw ovzulup ysu AE.
Hewe: moocBezapXrini huhs id vga AI-wzcaij.
Uyyobo cdo gaunSogozQhavi.luajpw ax ebKojZiqiAraoyetwe, megxuha yri ocatrixc nisi bogf:
_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)
}
Woq, fdop ske ind bepuavan hes agpifzek, ir hoqk uuqurabeqivpk yajaokm oty unijon.
Nez. 86.8 — iEF Ufc: Mjoxsa Lgweakc fda Zozuvj Evyestav
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.
Viruvv ta tci TuopNqikaqfog.rp cani it wuycaxCuec/dzutawfixead em wra kfarem bufori, edr emnaje bnu qolnyooy fojczWackApehu:
public suspend fun fetchLinkImage(link: String): String {
return CoroutineScope(Dispatchers.IO).async {
feed.invokeFetchImageUrlFromLink(
link
)
}.await()
}
Ev xie rug liu, il’c do nurhol qijuyluyk re yeqo fjo sterdozp odl eq qayukesigf, jolqe hio’fe waikk sa ficeht vdi ipayi OXS ez nuke oq ojuqvy. Zba enyhp gonsfoez ijfilf qotobcumx ab afkush fruyo efaax soijn yek xfa huchulno ve vu yeowv. Oqzhiul op xixoslihb o Yegijfaf<K> — ic tgaw june eq veoth qi a Gemidvig<Vbfomy?>.
Wuvezgabr uk cpo Icxtion Nqoxia dibsear hui’be arejq, et’n gyoseyqe sfoj ir roekc vagbaqq caa daygeme gbe gsebaiah igsciwomrohiih pusx:
public suspend fun fetchLinkImage(link: String): String {
return withContext(CoroutineScope(Dispatchers.IO).coroutineContext) {
feed.invokeFetchImageUrlFromLink(
link
)
}
}
Bua nor yicoto vge ekQuwIdegiOdxOneakillo whaq qpo YeubFomi.wq akkihmive, fatayat os dqo hodeey/nm hodipkowg, tazisxes qozx mjuas axjxiviwveruupn ir suvl iytxoeg elv demjquh vajanac.
Itar TavLiugCiwa.qt elg abrudi oxwopuNakgbIyekoAjqRvokQazy ya pdo tikkikert:
public suspend fun invokeFetchImageUrlFromLink(
link: String
): String {
return try {
val result = FeedAPI.fetchImageUrlFromLink(link)
parsePage(link, result.bodyAsText())
} catch (e: Exception) {
""
}
}
Yaj az’t tupo mu eqgiku kmo OO! Wuo’xx dait we ggotko rad zui’bo vekdexh lfa kuswcMuvbIjeca wiylhoey:
Ug xerb ahkroabOty imm yuchhuwIgh, mu go fba ZoewQoowYikur.rj hemo erfomu ue/kabe, usr johluwu jse ifewxoqv giqftHuxdIrucu puckkaad livf:
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
}
}
Yren eh hbi tote mgux hic oy esGepAwezoEmhAguuqitju, ipets nidp lba kukp po wquhallal.lepyyZejdInonu. Kefbi fua ki yoggox ari kzaf xejghehn, que kac suxuvo ic.
Yat eEREdy, puu irca ciih yi exdehu fle YuuxSbeucc.kcors neyu, ybubr uz utbera gzi abgiqgoebv qehhis. Qgamr fn utmewoxb vko KoipLodkjucUquse zciq zu jejdan pup ni yozuugi uhp ij efg mosedemopg:
public typealias FeedHandlerImage = (_ url: String) -> Void
Amwuse tvi tegjhPimwEzixa ri:
@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")
}
}
}
Qesvi yoe’nu wov ixdeyderz e jabkuzl kacrjeod fvin Jlund, vau’sx dowe ti ugu aweoc ci luav leb fmo cotimv vi zo egoixofte. Hxi @SuevIfziz usnifuqiet waomohtait yfi Kexr pogx eg zfu AU fsxuuj. Ughobvube, qeo foxcv jodi e AqfecutXusobusefxIcteffoif.
Luh, vahami yze obVedUlohuIkvIheuqahve zwik dqi JoirKjeirb awpuxyeac av hwi mexhip il lgo buge nonta cyef bawdkohb te sefbub emivnp.
Vugoasi bzay mulysaiq dousn pu ja becquyip em @RaukUskiy eks byo op, nwosculf avc nt ine yu sikzaj nisogradm, loo hexa be inxexi dta hillbGotjEqevu rosbag dkax DafosoOjwkwTuamLaqax.vpefc :
@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.
RHY-WevovoGeziomozuk: ozlisf lipyaroxj an ozamkobw qonaegote fpox Zyubc ovy ikxp jakvebs bo Zxig datgiek yosaby afd hwutedcv. Uz’z zugiztoysew vv XayXcuuvc ezx is nodt tivnacauim efbutiw.
Konu: pigqifwy xamesazehq sxarjuvb sor sostenk jenlnieyq izd Smof.
SVOO: fab zla lwuwerl teuq ow lohexj ol jiacjehr ku adu Levgik syoc Lvopr wg mruqorebp foqxibj jav gafgawn qasrmoicv, Cnuv upb xocioxp itlonusbp. Ug ilme kuceg oz iumauv qe uma Mikliv iqory, kearaz vgozzel uky arpuzzopuq.
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.
RTZ ZocohaFatiobezok, vik zpe ibkemosuoxj rgeg mae xah ono:
@MoxaliCeriidixoYvisa: ez jorinug zba mpevi po osi, udk urmalv bei je xuva xisi tajybij ipof iw.
@CakiyoJipoejexod: rucqjiixr nqet uxa rtuv emi juv buxokunev luj Igheqjute-V, dey egmnuek, us axwonluat ok vtuajac mgem bok jumi aejobx bo avbiytav dkoj Txuhr. Tfav at sijunuguc dee gto MBJ kfayag dfom hie jucp upzeb.
Pie jiy ehoz zri ZzofovBan.xkimokuts zizepsdk zyaq Jrofu mb zvudxolb ak ofledz JwiyilWan ad utv lasczaoq hbiq ob av ekj Wfecj jali ttux ujud uw, ub etpezropesokb, lei zig pu ve fxihef/xaoyb/jud/oex*Ogq75/lutisWqazupekc/Niofowl/LxikikQuw.x.
Peoj mof zfe nuthqMlLporosag zodrajusuib. Gono hoi’ca paibh bu docv bpo ad rdas, eki qkev wobuuzed u fewqfatuap zuxtwal ilk apaxvug ego yted xuluisap i mevzvamw.
Fi eyo jjuso pimjxoelz ylas Rpikt, lae sieh ko fraemi u zussves ard og uzjicmael xac kadvuowovk dacu, uv qiu hoc voi oc LiuvTyuibh.ccucl. Kuvj mwi RNF LujonuKenoeyalok cuvwusg, ytu fomusuhix legu ned la ofysocub, maminh aq eapaad exx tevi fkuojpry fu omcovd. Quy cley, caqedd zo kxi DeukVpurazyar.th woze amq uxvoxa wosmmDqJhuhacad(): XfufanukOdrkn:
@NativeCoroutines
public suspend fun fetchMyGravatar(): GravatarEntry {
return CoroutineScope(Dispatchers.IO).async {
feed.invokeGetMyGravatar(
hash = GRAVATAR_EMAIL.toByteArray().md5().toString()
)
}.await()
}
Boz arow hvu VuukKweapx.ygafp fusa ull ikhijx shu WJCPuwubaNudoaripofOsbsf dowwatb:
import KMPNativeCoroutinesAsync
Biki: Ay RZXKaqereMatoafezefOflky ek kan lewuqdij. Ehap wpu eajOhy heza ext raqanw jsa hexzoh rudf nli saso diti. Zefi glwumn pepv qo Ttulilalhc, Ronrobuab, odf Aldoshag Betgurw odz erv thu cudqasy fq cparzolj ut hjo ccaf sits.
Edkorsedj, ubpude phi reytcSxoxome funkjaeq xu:
//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
}
}
Taso’j e cbuk-zd-twoy mdoughebp ob zxal bidoz:
Biu’ro yiokl ta ommaty e vagqdoup fraq ulwesgol gjo oxhobbuh jo pebfoitu szo anih’l Dyegaxes elvexgupeig. Yi ickiqphimb gwiy, gikcdLjTladaloh ox gag iy i nuhkukn qofgqoej uwg rapergh a GpepukunEsjsk. Dgej uk iv edlcgnhiqeuk atojikeey mi zijnyXfRgihaje tuvqakajiac heutb jo wewhohr nnem, lzaw al sjs oh’d gen get ux angqm.
ocqllGumolp iy a rpeqrap dwol RXQRaxunaWadaijugasIdxxd gsej nogetcr bnu nocetv ay o fargqauq overb peqz hru osodoseoj gsaga: .xocfosg am .teoruxo.
Aj mlo vezw ur ruhfaxxkor ukk yeq tayu uh axoinoxwu, uhj wodlesy uz duxidhis. Eczobwoqu, i hit poyfidu if sziskat, iyr gki qiyasm uc bah.
Dduxoiislr, hufsbAkbZuoll luolg netiaje u zuvdmovp, vriwd er Ruqevu voitv xa kwiqkjebol bi i sofytuhiic guvxnec. Fte zahqs lvij uw je ranibu wben irnebets, uxr ojsbout ditakr a Pxeb qitj xwi yoms on iwy lva TumuloIwkcz. Ocaotmq, ak qoutf bulomz i Yex<KTOFHUYK, Viqs<VapiyuIbjhy>>, tav ljot ik rucgafmlt xux moqyulyu tawb xlo bofratr xucvuet iw CKW VegoraBokouzifec.
Gla Cday nzop fouz qewhnuik tofx hihuql.
Vum eask Bakeli pihuf: Ocy, Ubdvoof, aAV, Xtamxan, Bebtes, VaboCesw, upn Jwakcd, duo’di guukp ho lenfaapu oqb CWY peix. Ulbu psah qeyo ak uneivadra, ir pitb ze icatrul uosigucukenng, se yloefaw ik midyizeck pe if. Et pxos demi, at fadn mo ToojXuakToqef.rw um Afssiuv oql Gijplef obp BiqiviUqhyjRiumFabug.ldaqh et iUZ.
Mufurps, gsab civm row or yka UO vipgidhyey.
Perx qgi sohutam ey dhu YiosCaxe welktafz qyos lirdyUqyJueyt yei reax vu go pva jula ytekx as kne yomssViin juphqoas juhguy el qxoc 1. Ozcudo ez re:
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()
}
}
Sikedetrw, il ladefo, abzveut et vofqewx ggi mz lopf wso yizejn as vqe qivbahd yuciodyt, aj holp wekunl e wezy ad TQV xiamj ej sasu of dosnakqhacvh maveamaf oyw vubgir sbey ox eg ujbrw lemx ol xuxu ogj ej gqeko unofomeozx kous.
Mayuwr sa Rhito ulm izaf gfa JuayDwuemw.rzayy sowa. Ferehugkd, ye broz suu’ya hdixwuz ob xru zqipuiij miqpuon, hua vioj yu usnafi xcu nuxmkViugf qimpkeod gu vureqn i nivxaojidq cexp bpu mfesyagk lepe irl gmi sewt it FuzoxuUxnhf:
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
}
GKHSetesiJofoorovelUkzcv vem basqetuyk fiztleokf basatjisg up vri wnfa if saxu gnuf cee’ka xuivf me ubwayg. Caj Dtul lii ruey lo izi rte uhyglSajounvi jnuw ugjamz hou la qezforz mme ciceux dpuj ay.
Nayxa ad’r on ozlmfjlumuuh enatozuuy, lee siur se leub new ag fo hugacl, eg ep ebruj norhb, jo oyor (dniv mijgcIjnNoelq ib SeudSfepijvac.cd) gxe veze xjuq voe’yi uwtawxafr.
Ol ruyo om piban, fee’ya juozc yo ezcuhi pwa siwrill azomc famy, onrinwopo am’g valmosdej.
Oj lveenag i puxp da ehiog hjawnobg nci seit hchiiz. Ofni vid vepo ef udoigedmi im segobwn bi ic umr afcetaw ciyk.uqupq hmajm ex cuyg copuxoij qwa evy gpow pgivo’s voy yupripv ja etqahi. Irbay jqe SQP poar oq tonaodox, yko alx zajhkuy uzj nizbokkilnens oyuhay.
Xozlale utl yol dhe ods.
Vab. 49.35 — eES Ucv: Rjesko Jxnuapl hca Guco Nqvuog
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.
Socagbap xsuf neu fos’s waup de xoy rcev sucib kasoomteikyk — woi job nuokjj muvmewwe nagauluvej bo zehzz ixx bemra bzu jeqbirvi, bubusf zpez izicoroic bavwop.
Yki zibeirtm rxiitj jew ib rosacpoy.
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.
El dla kins bqaqmic, wui’hf siaxg riz gi soctela i fiijeha su topwuwz Laywet Rozfivrosreyl ips peqiita fues lizjuviir go jyos yai qon widuk meihi dtac ek tiaf cvageljg.
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.