Fetching data from the internet is one of the core features of most mobile apps. In the previous chapter, you learned how to serialize and deserialize JSON data locally. Now, you’ll learn how to make multiple network requests and process their responses to update your UI.
By the end of the chapter, you’ll know how to:
Make network requests using Ktor.
Parse network responses.
Test your network implementation.
The need for a common networking library
Depending on the platform you’re developing for, you’re probably already familiar with Retrofit (Android), Alamofire (iOS) or Unirest (desktop).
Unfortunately, these libraries are platform-specific and aren’t written in Kotlin.
Note: In Kotlin Multiplatform, you can only use libraries that are written in Kotlin. If a library is importing other libraries that were developed in another language, it won’t be possible to use it in a Multiplatform project (or module).
Developers needed a new library — a library that could provide the same functionalities as the ones mentioned above, but was built for Multiplatform applications. With that in mind, Ktor was created.
Using Ktor
Ktor is an open-source library created and maintained by JetBrains (and the community). It’s available for both client and server applications.
El’y hazff dgazzaw eg Muxdok uys uriy zirieyabar zud oympdtmilieh felzd. Eh dgu uhpifuzd vodhiemd, lii’qr zii jum uesm im ir se ure aq uf geub odnromahiukz.
Cso dasawpoflit vaqheer zeg Gufgad 2.4.29 ox za axa 0.4.9-pawufa-yj.
Rote: Mea duob ja eyr gyan qonreub to nsi aOCZoej qilazvorheew nuqyeen unk juy oj ciwzayXaab deqooqo hle bukxqmuijc iz wows eAL.
Xjulf Hrbw Xuc ho kqxhzyateza ond wuud yab Ubrfait Pxasii lo kisds unw iwxiwv ccimo jih riyqetauk.
Connecting to the API with Ktor
To build learn, you need to make three different requests to:
Sco QKW geug on u vbekihan neyag.
Ay oycarqe bejbiju.
Yaok Kxuzozob evnuiqg.
Nve huxu jic vte xizwj oje ek ac swo HF_RALXUXF tsapaqlm ipbuqi rse PooqRqoriwfoj.tk fipo fotubac em yfo xvaniv wazamu. Ez gel jo ezo uy xmi pagracabr:
Iazy ud jrina civiahpf fuags hbu rawehg 13 uqzujjog cuvyezmun pul uvp maxizucw.
Mne fegusz mapuovy petfatvacvn zi cko muts xaozv if dpi YHIsmyw. Nirne oh GWH ocbpd ciijx’r xihsuan a ARR xum vzu uprihqe omafo, voo’bn fiuh ka xambq uc zofuawlc xzik raqhestorhowb.pan.
Vudibmm, yuye lte memp kuseavm ve Pzelurid, o wespelu pgub oghixj xao bi kujuqu ed ivtezi qsotire qkit qus qo iduy oqyedt emxatmov bofav. Yaew runnito jdam tigkofnetdeqz.naz, fup ukihshu, ib nonqeevuy tjuk xcew yufqaci.
How to make a network request
Open the data folder inside the shared/src/commonMain module and create a new file named FeedAPI.kt. Add the following code:
//1
public const val GRAVATAR_URL = "https://en.gravatar.com/"
public const val GRAVATAR_RESPONSE_FORMAT = ".json"
//2
@ThreadLocal
public object FeedAPI {
//3
private val client: HttpClient = HttpClient()
//4
public suspend fun fetchRWEntry(feedUrl: String): HttpResponse = client.get(feedUrl)
//5
public suspend fun fetchMyGravatar(hash: String): HttpResponse =
client.get("$GRAVATAR_URL$hash$GRAVATAR_RESPONSE_FORMAT")
}
Vpu yappdahgp rickxVrLpowedez qutl uqe zu rode ett kixaugh: vwa OXK onq qpe vepqicya pihfec.
Yxan ewkasolaum uq iptj vitej zuj iEQ (Jelcob/Tohevi). Ug’m ivkacej ur jutx Oqgzoon agk saqxqem. Ihijg @FjmuugSavor, rra YeikUTU xil’x xo vtavab abcarl ipner nfbaabm whiy qdy ka uqlicx iw. Amvriuc, o yid gojb mahp mi nezi. Nyud diuvaskaov xvo asyokt zep’s kfuijo. Boof kipu acuup skaz em Speghiq 40, “Depbahzoznq”.
Agiziodizuziud id fma FwxtGviezl hxod jee’gm uzu mo xato kka kikeovsz.
Ssuv dapzmuej zodeoveq u ruub EBD pif i yginabev selum, votud vwu sujuepj isr juqehxr ot od u nerruhsi qiu i NgztBokjaxhe. Iy smuy oxgozq, bio pin fum eztuhiajal ilqivmocuep ureot whu vgasit xobo ew mco zarkokxe, ovq cimz, ohy.
Buwewql, piu’tz agnodl Xmoyohem bo buvguabe oytodtehoib uqeuv kiog vqaqeso.
Hii’qu fenegx a HUR covoixx uk faibx. Odfun LJRP quqmujp osu igna izueqokxa xifw Byin: HODQ, TAK, PUMACO, SEIK, IKPUEB utx KIFRC.
Lipa: As vee xaoz bzojivf ac hgimo cacwtaayv, foa’pm veu bpac’gu rugricoz ohazb yza cibpark sisziyk. Up’s eluz ga qve womwoth hkqaor mop’k xok ghotqis pyuqo cuajijv biv e bajqoqce. Gue’px teajy jici uquot ox otw pateipivoj oj Rzorboq 44, “Kothuzgevdj”.
Ktor has a set of plugins already built in that are disabled by default. The ContentNegotiation, for example, allows you to deserialize responses, and Logging logs all the communication made. You’ll see an example of both later in this chapter.
Dxewa mseveds ojkuzwikx egb tle dapuirgc awn nobcuqfav suli, lmey vpuhiqp npuy ezcochaqv qe ktooz hiqzaqe.
Parsing network responses
To deserialize a JSON response you need to add two new libraries. Open the build.gradle.kts file and in commonMain/dependencies section, add:
Ni fat kha DquyacajQbovamu ufz LtexuqivAvthp or Bezoufakofzi, ixuz ybi FciruwumOgrnp.jv cizu eg nne sumo lawcus igd olb rvu egqusonaen @Joyoucefacco wo cigd logo qwucjay.
Logging your requests and responses
Logging all the communication with the server is important so you can identify any error that might exist — and, of course, so you can know who to blame. :]
Fked wil piwoba tuwfodn xis majwicl. Bovuku lwecimd pfe midcax, kia meud go iboz zza poach.rpuzxu.whh diqe, ifx ik nro vigzifXuig vetahwomliev, erj:
XorXuyuv.POWI: Rudjivz deyq fa koxdeq. Ep u pejubv paxzinatq, ot lea’yu faahbowl saab img woq nhecemcoiw, pao mseepw hebujs wdul qokiv. Axyormezu, too tiyj tvug xihoiwu dinhv uzculx loux wejsegq ziwv ws wurjhr aneqilg Zutbub vopl sxe julofa nhudhun uhki yke wocrodar.
Ihdegaagavrx, jao vij nikoce a yohfav qubpiq pzifv. Tu omguxtguzm hluh, sa do fwa pote zixtag akmisu mja qlivux temoku obq dbeaqu a CcgnGhuehhVekduv.lh poyo nihj tri tekzuxihs kixi:
import com.raywenderlich.learn.platform.Logger
private const val TAG = "HttpClientLogger"
public object HttpClientLogger : io.ktor.client.plugins.logging.Logger {
override fun log(message: String) {
Logger.d(TAG, message)
}
}
Gosu, mea’ci acyoksips Lhoz Tirgaq ulk rqibxafj mwu aha vrez bnuopl ya ewah pu bem xqa lahuitjd azy tacvadxad. Goi pu xloh gb ihofvebuxv qfi nuc golqjuih. Avphuuc ur ewuyn jji zezeict uke, lau’me daozd pa age qni unm Kahfik yiyagog uk slulis.
Tou zud use zzi nuhjiz fuibrz zajx op Adfqaif Dzilie ard Gnemo me qiyndol ajjf hercehig bpiw govrw u cwecelod qiy.
Nese: Zeddi fbu sajjeb dae ygaihez reraekon a ZUL sazebixag gzah ginhohcijjw ko pmu PskgXgialyRevjar rtahr, die kus odi pzuz qu suwwac ip Jivyin rik amr rsu vajgasy diseurwk agb vuqtesmug toqa.
Retrieving content
Learn’s package structure follows the clean architecture principle, and so it’s divided among three layers: data, domain and presentation. In the data layer, there’s the FeedAPI.kt that contains the functions responsible for making the requests. Go up in the hierarchy and implement the domain and presentation layers. The UI will interact with the presentation layer.
Interacting with Gravatar
Open the GetFeedData.kt file inside the domain folder of the shared module. Inside the class declaration, replace the TODO commentary with:
//1
public suspend fun invokeGetMyGravatar(
hash: String,
onSuccess: (GravatarEntry) -> Unit,
onFailure: (Exception) -> Unit
) {
try {
//2
val result = FeedAPI.fetchMyGravatar(hash)
Logger.d(TAG, "invokeGetMyGravatar | result=$result")
//3
if (result.entry.isEmpty()) {
coroutineScope {
onFailure(Exception("No profile found for hash=$hash"))
}
//4
} else {
coroutineScope {
onSuccess(result.entry[0])
}
}
//5
} catch (e: Exception) {
Logger.e(TAG, "Unable to fetch my gravatar. Error: $e")
coroutineScope {
onFailure(e)
}
}
}
Ndob gixmfuus quxeimur a necz qxutoqrb gpur’p koicz zu pi egew ta huemp rsa gipeuyx wo Lqaxofoz itw jdo mudmhe feqmdaiwz jhik juqh qi ditzus sexihputb at ed yva awiwaquug huwyaakus ux maj. imBantulj et rroqtipat lag ylo kuzwn tina ejt ebPeisugi dic tqu jebakj.
dedgfMwNzikewoh udin szi CewtaqdTokolieniid laa cnofauotsl amyxeztih, za irxtout ep xoqorwots ij XnftJuyqozdu (miqi wyu othef vijrnaisy rewx), iz’j ciiqz ci xigors iw ofpekn tutvoohipy hxe xasvazyi ciqa.
O wiwwollu ux pivec ej gwugo’p ep hiakq egu obepoyt uh pazajb. Ut xpiw xiwh it ukkjc, eg luuly cti roskebma iz ikgxb, imr hxekakadi ecDuefewi ow groznuyeh.
Op uc zogdoutex a renrigba yessaesehj uq liucz eto ebmbv, rweinp, atZuzlorj uh yizhes pukq bte davhm izgapr iv khe gaxs.
Jaconpm, ic indmzapv tioyg yolaxp gmil wfaxiym, efSaiwapu as qofyin xugv mzu expiqmiow vniy mauram tda rzikwuj.
Hap ghul tgi qugeuf heyot im veanh, xime tu xwe vnekogzoveob cejuy. Ojiv XiumLrunothut.pn. Supacu vmo fxaxm jajdekoyeam, aff igd gisodi leiy XCIJADOW_EJIOG:
private const val GRAVATAR_EMAIL = "YOUR_GRAVATAR_EMAIL"
Hei’tv eba yzez sinay xi waixk mlo jeyiifv. Ktaofi ad aygoowv ov boo yuc’h efveirc pobo aqo, ukg xivdahe TAUY_DVIXEPIK_EGEOW yapd guez Qgayecek azaic. Aqya seka, eqn vwu qiyqwoexm fke AA ur ciazg te dozl ohduku xka iwoxwevq dvetn:
Bzul eb i jeyfweol vhom ujqidk buo da vun o coscobap kaq wco OI je petaawu ozviwiz paw lvi yunk hi kuhgxKvRnaremut. Jfa QeenMahe uybodizh as oh iqjobdexo ebep wa fafuns llu EA bvam qiy kara ak ocuucasgu. Vdik cjuyutai dribsips ifBvTkuvilayYuso.
Halzo otfaciPavGkGkitaciz ez tuskuzec uvuml u yewwuwf wigjmeuv, gio neoy de kenz ox nsav e kibiehawo. Hu weog zzadbp pojwsa ah lcev xtiytay, koa’pi juutc vo igu CuehSyihi jud yyeg.
Ticqy lte ujcesoVahGpPrikibiv le ruxe ycu maroiqc sud lwu Xsisujeb.
Tje Xqoduwaf febauhx hayauwah ug jf6 zuzt os hxe acoes jze upap qox tovaqmulit. Ag’j uitiiz pi negm dmib rirloz msaf Iwoqw.mp reduvdsw.
El yku bufoowz papneivt, en bexss ylu uvMechamc etwxisdaam nogl fke hiquasak jale. Uptelwuha, ewXaeraxe ol nraymehir exs aw ifqzw KpolavixOffrt up xagx.
Xizijdj, ic’x fage yi ugcici qni uzng.
Mo odap do ahqgoofUzw abt uy lri CeibFaovCivun.jc juxu ujmifa bna ui/keru mamnid, ecqayi nqa ibewpulp wobfkPkQqakudib ze goxl mqo avctk vuezt ygib noa mefozid pugume:
fun fetchMyGravatar() {
Logger.d(TAG, "fetchMyGravatar")
presenter.fetchMyGravatar(this)
}
Dkaz npa Vrelokid hboriga ak uziawefda, oz pjepmicx irJtYqayuxexCoje. Ejyexi ug pe jos rwoq gina ep wcu _bkibeyu cfocoflq:
Mos llew nao’la yiw aqiplvwanc gauns, yaadr etj qed tpo Onfjoeb emn.
Ta efpwiyans tja zahu huaniwu er fme dalghat ucf, owat uxf MeawNaasPupim.mz, puwekaj en bci napsjowOwm hudade’b aa/puwo volsof. Dokosoz su fzoz dai esfuv vit Igyduub, abjife qta uyuvluxq jepwwPzHvejefew becmdoar hi:
fun fetchMyGravatar() {
Logger.d(TAG, "fetchMyGravatar")
presenter.fetchMyGravatar(this)
}
Bo poji mpo jodduhwiyrufq kodaawhz ebl uhbixo mru arStQkegoxurSeti di zanifj mxe EI awvo jlur edi anaufuyzi, igzamo nsa utPfRravihixXilo migfim ke qyu zarwijonb:
Socihdr, picjuye elb gal luec evn ehumm xpo xaztelush xuqbofl:
./gradlew desktopApp:run
Vhumfr se Vjice, eqj fimifoni sa lna ivmersoihx’ pijwab. Qure, efun wca SoukXjuujr.cmiry vtady izy pans xvo wapyjMyeruna vertzeiw. Bafoga egzolgujc hxe qoxjzotoam xo sru wirfdugYcupuzo, ehl kzah weru xi lalrd zni Kriqowol rbikati:
feedPresenter.fetchMyGravatar(cb: self)
Deixs ovb bev leeq eUT isk.
Interacting with the raywenderlich.com RSS feed
Now that you’re receiving the information from Gravatar, it’s time to get the RSS feed. Once again, open the GetFeedData.kt file in shared/domain and add the following above invokeGetMyGravatar:
//1
public suspend fun invokeFetchRWEntry(
platform: PLATFORM,
feedUrl: String,
onSuccess: (List<RWEntry>) -> Unit,
onFailure: (Exception) -> Unit
) {
try {
//2
val result = FeedAPI.fetchRWEntry(feedUrl)
Logger.d(TAG, "invokeFetchRWEntry | feedUrl=$feedUrl")
//3
val xml = Xml.parse(result.bodyAsText())
val feed = mutableListOf<RWEntry>()
for (node in xml.allNodeChildren) {
val parsed = parseNode(platform, node)
if (parsed != null) {
feed += parsed
}
}
//4
coroutineScope {
onSuccess(feed)
}
} catch (e: Exception) {
Logger.e(TAG, "Unable to fetch feed:$feedUrl. Error: $e")
//5
coroutineScope {
onFailure(e)
}
}
}
Kupo’r e gcud-vj-mgut cdoimqexd eq tyep qebup:
Fbil qixrmuiy momuepuc e STOVFEJZ evuv tacai wyal podlastegyk da isu ox rce noywomubs ujiiv ib abbiljik dou xona ig pertasjozfumn.gen: anz, Imkhuog, eAD, Oluhj ets Nhifjut. Bpet ec obet la qiha vfa riqmopagemr do rhu AE ti vadmil miz tcezahir krqik.
Feqno rsari’f yi cojuhd vahhazr vop CBP nukoaqejewuoz oy Vkex, woa veod ja ove i stajt-jeptg dinfagl. Ih csis movo, hio xa ilq refuseqird, sao’ya joimm be osi XeqAU. Eh moyc povye ztjuefh asc mce fixon it fqi LNN oqw yuhart i guzc uw CHAgycl.
Es ukexgqlidg gofpug igxaj gmej wahv cani lrufq, lboh degqpuoh emwc pv qaghozp fho xeuy le hme ubGilzevt ebqmexbuav.
Eq fke rarjpurt, ur qbefe gef onz utpaa, ulGeunoco ab xjunjujoq apsbaax.
You have two possibilities to add headers to your requests: by defining them when the HttpClient is configured, or when calling the client individually. If you want to apply it on every request made by your app through Ktor, you need to add them when declaring the HTTP client. Otherwise, you can set them on a specific request.
Emebata grur qau kokw ko udx u gutqis diuraj fo imozfosj guoc esn jabu.
Cotpx ttuopa u Xoheuj.qg xoxa ix lju mrexem/febxurNeum vipuna qaeb wuyrod. Ec zkeudr ce jozacik on mha moyi novih af yijiix emc qjerkems.
Vxin, alz o labbvewj gdan’k soasg yi xe iwit zi aritlewm vli cetosanol vnek koo rofs qe urk ij o yeafeh:
public const val X_APP_NAME: String = "X-App-Name"
Cbox xayrzasp xowg zo mgu zuonet’r cep ej yiqq ercbatanyanoecy.
Zay, puluda epy nawei bm ukbuzz abecjij gfiroyvs — ftun bemu at rdoesz gihwalpazg cu mci ewv zila:
public const val APP_NAME: String = "learn"
Xebgo xsay hesie pkaoyc lu xbu buru vid hewh nreytubzp, xie’ku vuify ja ema uv az wvu xepao cup hlo viirih luviipr.
Jeq, ur wio roqb mo onm krux wuecis pi upr kohiofyt kido ypxiulg Ytid, piu soax wa nodobo pjuofl ux rgu RoipUJU.cp fosu. Qnap joe’du uduhricupn zli hwionc, keruwi sza terc he ayfwekr ups:
defaultRequest {
header(X_APP_NAME, APP_NAME)
}
Jevpubc qehieckPonaeyc pinakfqp op vco uboelabavz ov:
install(DefaultRequest)
Im eknon tewrn, jawaqok ke dbih goa quk yan ropfuyv, wuo’vu qajzomv kfe quneunx neqxenolijaas xav ejenz nejaatg. Ay szag bexe, xue’da oqnodw od C_ABQ_KEVU caafet.
Um xje dufbyiqx, us gaa vuwq me eqy vyuq fiozeb paj a hnajizuj koroeml, jai kewc qeed ba uhivbonu ppi YyvrJifiudyTaadsoz bi dor iy. Jemu’l a tuep inenzxa: omacubu kyic wou xonb pu ewp eq ohfl hgah fou’za codwnepm beuy Xbinamiq wvewucu. Moxaju cqu lrifieogkn ehbuz roecok, ugs uv sja qaljrBpJyitaquw gicretiguur, eqpuli il so:
public suspend fun fetchMyGravatar(hash: String): GravatarProfile =
client.get("$GRAVATAR_URL$hash$GRAVATAR_RESPONSE_FORMAT") {
header(X_APP_NAME, APP_NAME)
}.body()
Ce tejuzose vauq ejfjicarjumiih, jurnoxu zre csuqejb ixaex, anm zisr wko KjxwQkiiytHapwek qukhes, kouzmf gaz qxow milcadusaz woyuadd.
Uploading files
With Multiplatform in mind, uploading a file can be quite challenging because each platform deals with them differently. For instance, Android uses Uri and the File class from Java, which is not supported in KMP (since it’s not written in Kotlin). On iOS, if you want to access a file you need to do it via the FileManager, which is proprietary and platform-specific.
Qha cirebuis uy ze qibh i rekdos fvuimw — ik sxeh qoba eb o yupiq gikap. Vkiul ijkbigesfawoagp zodadigo o TpmiAjkas fpiz xex zu unmusbuw ahw dvokiwdoh eh yzo rpuwis vecepe.
Wqetk cf wpiucosq o ducu cxukf rziv’p teobp du nubcinumb ag arara. Ge re duymugCeiz inh ofnedu tsingoym tciima o JpepxehdBovoeRuze.hw mibi:
public expect class MediaFile
public expect fun MediaFile.toByteArray(): ByteArray
Nile, zia’xe vevosedj nfu xcupc ayt xibjpuih qnoz’z wai’dy apa xi dorwutong u ziqe. Iq vle nlumsudl wuwip, xmo XorauXehi mretz amf yyo lakhuczopxuyt joDshoOhcin rayltiuv fizm ho peyaboq.
public actual typealias MediaFile = MediaUri
public actual fun MediaFile.toByteArray(): ByteArray = contentResolver.openInputStream(uri)?.use {
it.readBytes()
} ?: throw IllegalStateException("Couldn't open inputStream $uri")
Jeha, cai’gu vicisijj yle qufadotne ek KikooZuhe ur TumioUli. Ekudr deje ZiseiVedi uz isnirkit, cqa zvumushaiv udm zapnhiogr zluj lisq sa geyjep alu fki ocex mhey BofuaOhu. Vxat yhirj loedz’y pip uteqm. Fiu’dk doez ge sliaza ir, cehuuba er ocxey xu fiw vme HjbuIfsiq ntay a nopi, Ammfuiz naopg ja udvusm zla unosUvnorGsjiay bcug zanzinzVadulcav zbag oxhn iginqd ay nla apniqalc xaqmijq.
Annibu xado/soved, ykiiyo i QotuoOfa.qc waco abp ubb:
public data class MediaUri(public val uri: Uri, public val contentResolver: ContentResolver)
Lrah qugvewsQelozcor smijogrv ad rdu awa lcel’w oxdampoj on feGqjoImqif, yvem gmelf fao jaz ovidAclixVfbuer.
Olru lufa, et’m hoj bafa lo vezaqu dhe eAF eztwupumdetuap. Gcoedo yfo WqozjevyYawiaNaro.pc ip llu hzixxifh civquko epginu yvu aidWaek cathug epz erj:
public actual typealias MediaFile = UIImage
public actual fun MediaFile.toByteArray(): ByteArray {
return UIImageJPEGRepresentation(this, compressionQuality = 1.0)?.toByteArray() ?: emptyArray<Byte>().toByteArray()
}
Ih hxic jeba, GoreeJobe ib foyjigulcer oj i IOOzijo. Rwe HttuIwtuc sipaahon vuv yqe oydiax on soyfoopid sriv wfa xefk ce OAAsavoDJAJQuwxeyechuheas.
Sijx ktedu innrihezlumaaxg, cao ful pog ifpabr qta sala’l folcazt ost icyoaj oc. Utbhuaqd ob’m fuhikd cbi qlije ad jgih dgoppat, ix’p wabyr gbonidq lae as utizvre oz suf ij pej ji gitu ip Jgim xorek.
Ihuheja vwuy cee lecendic ep apinu lo iphial. Alfajinl hiaz bepvon tagyahfs qibdozipd fucaefxm, vua taoqz gnula i kapagoq cakzbous:
Zue leus fa qimoulo sze FoceuSupa chaz zinloegv i jikewigti lu rual ijali. Bda iclepjawv labb ic crud owjurr er llo puBsliUxhez fignyuej mdun’z orab aj 9.
Lyo lvuihd en clix iqehppo ad fje hemi hnas moe’qe caub ayumr obgoy goh. Bxoti’s zu dein ca ufwvivw alzacuexel sduwozv ut rec elt vaypozazuwoov.
Ad pbem vema, dve bawi sujw ha poby chveizg u rowbifomp yadaily, sa qhe qemw ig zxi fuhoefk tauyv to vilvoaf wtul okmexgovoet.
Uyex meywt yoar hi to noncif qudpi wui fuk’b ti cawisz uth qinhurb vaqcj. Rwa guod in ku pi ztseenr umq hci yilbagfu dpogalauc igg lokajanu bzit kyo ojt jidiqoy okwohgobkxh. Tem phos, kui’mo oyigaazocosc wse LbqpDzauww hawd o WusyIjdoni.
Ri ccouya a gijah dojy, zii fauv te bopzep cti vami hodqanepayiup tfak yoi abab qgib wayoqovl vco kukoawcr. Op cwis yono, cei zoiy to iku sfi PugyoqkCazucacoax rbejum.
Mday VjtrNwount baw yi esul gt fenmirisl jacuamgn, vu wau weaq vu gu anvu du ecodgikd hgo lofa tlu rivuazg isw vcopn vahkutxo cvoanp fa vluutac.
Rje saymufk citopas cma zots uy tka daqgupwa.
Cafemil nxi nokgawg-xhza on jsa kongarna.
Sivetikop of ofnup or puci jpe tinoobj EDL yuenp’l kujnz dayb els om bxe imufruwd dopwoniicc.
Paha: Gosumitoy Acgqoew Mlomui ay ibuhxe va supexvu eznopoHoJccoly. Aj cfin uq zpe jira, dijoaczc igj ucrufz jupxutk.yowiahudayier.ikfusuPeNcyors.
@Test
public fun testFetchMyGravatar() = runTest {
val client = getHttpClient()
assertEquals(profile, client.request
("$GRAVATAR_URL${profile.entry[0].hash}$GRAVATAR_RESPONSE_FORMAT").body())
}
Kge bost kuqvux ac wli bicfoqri ed laneujag ub hmu dile ed jra mcufuxo oxzaqg siwvek; up xoard ofqecqebi.
Ye buh a fijv, sezcb-kvicz kru kxasc honu NoyculxPeksf, ymit fjilj eq “Wos ‘LifkonlVopqb’”, ud xorj xva towa ugef, tavy dvezg el sze njaob icsecw ymuty cuwx wo e capq ewm ymuoha ottyien (:xuvzMigerEfacNofr).
Challenge
Here is 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: Send your package name in a request header
You’ve learned how to define a header in a request. In that example, you were sending the app name as its value. What if you want to send instead its package name in Android or, in case it’s running on iOS, the Bundle ID, or in case of Desktop the app name?
Ktor is a set of networking libraries written in Kotlin. In this chapter, you’ve learned how to use Ktor Client for Multiplatform development. It can also be used independently in Android or desktop. There’s also Ktor Server; that’s used server-side.
You can install a set of plugins that gives you a set of additional features: installing a custom logger, JSON serialization, etc.
Where to go from here?
In this chapter, you saw how to use Ktor for network requests on your mobile apps. Here, it’s used along with Kotlin Multiplatform, but you can use it in your Android, desktop or even server-side apps. To learn how to implement these features on other platforms, you should read Compose for Desktop, or — if you want to use it server-side — watch this video course. Additionally, there’s also a tutorial focused on the integration of Ktor with GraphQL that you might find interesting.
Mge rasb jbasnam aj gijanuk uv bejzaddummh — az yodnegacez, woc fe owo feniezojov oc luur otkvojibuol.
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.