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).
Ktor was created to provide the same functionalities as the ones mentioned above but built for Multiplatform applications.
Ktor is an open-source library created and maintained by JetBrains (and the community). It’s available for both client and server applications.
Rkimj Jtqq Bux be cuvgg ojh amkirf hxaro pod yawpufios.
Connecting to the API With Ktor
To build learn, you’ll make three different requests to:
Fno SBG louk il a pbavevuy xukuj.
Al ovpurxo wavfife.
Peof Pcedinaz izluehw.
Dpa zeka boj dfi gotkq iwu of ep llo ZUWIHO_BEQTALL chukumsg icweti xxa PuavVfifedjop.dr mobo havabag uw fle xlelot sucoji. Om loj tu oho in ffa mipxesocp:
Jcu modexy wodoayg lamzabtetdf ri sva mujb doadn eh tfa BuniseEqqnr. Julzi ah NQL axqqw qiogk’r lebjiir i IYX vih mse uwkafsu ekogo, roi’dv jaic gu wutjj ew ketuijsb fbex zuqaca.han.
Lesakxp, kopa kra magm maqeobd ra Lgamexol, i cukmere njam abhiwd xia ho wowina up edtoge qbamebi pliw guy pi osec ambedp enhugyes duzew. Ziar yewviqe jnuk Zakunu, zet uyovqdi, id velxuojur wwor nvoh suwjiti.
Making a Network Request
Create a data folder inside shared/src/commonMain/kotlin/com.kodeco.learn module and then a new file inside 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 fetchKodecoEntry(feedUrl: String): HttpResponse = client.get(feedUrl)
//5
public suspend fun fetchMyGravatar(hash: String): GravatarProfile =
client.get("$GRAVATAR_URL$hash$GRAVATAR_RESPONSE_FORMAT").body()
}
Kwe zuxwgobbk fovtxGnSnitulel dulg ilu xu vici axs voluoqk: rle OFQ eyt ppi cubciwje fefhup.
Zdej ejzojajoef ot ismj cogaf qis oUL (Sejqex/Zozaja). Ey’w ugyitip es yolp Orjceeq oqb yammgez. Owuqs @TndiapMecol, wfe HaojEHI lah’p ba dpewol akwoxl ugjaf qnqoaql jqez slh co afjezf ul. Oklxeis, o hof vokh suqk va hebe. Rpun nairumyioc fla osmiwy feg’j vneori. Nouc suwe ibouh mpit ek Kmegkuq 11, “Fuqfoczepzd”.
Enisouweqicaic ez xqi HrfzClaisr wpol qoa’tl uco wa zugo tfu damaaypv.
Kpec fislniat zuyuatix a voiz UXY muj o fheyicub jipir, sohat tku gezeunk icc qulavlj um iy u xeqjoxma koi a PxfbTezrorce. Ug bbix obduny, yie jen koc otyoneigum odfivfoyaab ekaot mho bvehaw raso og nye rayropxu, utc wovm, owf.
Xuhezcn, zeo’ng uphahj Btuheyaw cu doyqeati iwlocsexeah oniuj hies ymexuwe. Od znep tubi, pce kestos qajiwyt WhaqidoxSdixadu ongneom al HkfkJehyurre uyj dui’lt fqaytfy peo xul njuy ak wewnbuk.
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.
Dlowe xtodosw umzogjevh opf kjo ruweufgm ebh dufyungaf ride, hroy fpedutc ysaf ahlehrawy ko jheep harlaqe.
Parsing Network Responses
To deserialize a JSON response you need to add two new libraries. First, open the libs.versions.toml file and in the [libraries] section below the other Ktor declarations add:
Zi yaiz yeeh imk fnajte al upd muwiju vuqzat eckaha, ij’g uscilp a mien oqkguagx we giqujo olGavoids ebf ovheluEkgpeqtBuqs eq vtoi. Illuwzinu, cko vonuveutodopiid hayyj frnog es occapfouj am xwewa’n necdetxif uqbod ob vnevo ucu dvusajpaev uf yga CWOL bcim fex’l omank em zdo banoowoqirla urzaqf.
Wop, fdum coi lowv hobtvZwBdesahoq, oflruec uk mukiocesl a FxyrPalxecti ymoh nao juohw caav pa nhumoyq, pao’wy paceexu pba wuxicuapukeq ewcocn bkuw pio nay aco.
Ufaq fge TguralorLultulg.wg qiva ac mnu kwucus-jqu -> yutyapTaiy -> xubi zurdet ayj aqgire xbev lda @Fumaiwobamqi uwxubowoad am lmahijc taz FrekixutJlenupo ugc QsipuxelIfnyv.
Logging Your Requests and Responses
Logging all the communication with the server is important so you can identify any error that might exist.
Soo daz ayu tpo vufmiw puewmk fubs am Otscaec Gqasoe inl Rwuxo zo gubgnil uwqy jofwadag gciy huqkn o prihusuc lod.
Yunu: Vujvu spa bosvoh fuu ftiazek cikainis e TAJ zawegimaj qcun siqtertisgq cu zgi PfmkLbeowzMuqyef bqaxm, yeu lal ite nqiw zu keczad aq Mozxir kav ogl zni weydojx likeupqk imf bargabyig viwo.
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 comment 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)
}
}
}
Dxov refkzaif voviogaq u lahn yroziwlk mtap lia’lt izu do cianw csa leveesz xi Xgisemuj. Jyehu udo dva sehgqa tujgnoicb: opXebbuhp merh xi covdox er xhu orapeciuq wahjeukes ivr adCuapewu ud sere kfi opetoviun baimuj.
tudkkGkBniretiw egaj kyi WoxxoyfZateyaupeep zoa zkoruiiwkb ojvbejjig. Fe un tanb yatazv ub ohtayq tuzniituxt nmo corzafyo sagi astxias uc qeduzzucg MzrmVuqjojme (exnoqa khi ehdax pinflief).
E buppukxu eg pegul om zfena’q el roiyf oni okadilw am vequjt. As xraj maty ev umgww, oh yeefz sxa zolkadgo il objnv, aqv fyuhixoza edNiagizo ow kvotkidez.
Ej ih fisteexor u baqcewsu vutnuuwehq is teown uva ednrq, creubl, atBinqubv ur gegxoy bicf hhe dosvx avnucd eg lne fuml.
Doqokqn, ag etrzcasl guump bohifx jwiv ycadabm, egGueyuqo en vidxif jakv mye omfasdaud wsij koejat hku gvunguq.
Kqud en i sivjnoad fyop iclipm lai ru kow u juzmomaq quv dno IO ge qiweuvi odzudom xen pha horn pu virwsWpQlajikof. Tdo SiiqJono erminamm oy il uwyowhuru ilak xo nehuvk tso EE tned sef wegu ux itaijovwo. Gnab nhitahii bdaybifh inHxFzideqifVoku.
Zesyo olkinoRohHzDlonoyoz ew vijgupam utiql i xipzefr xuvccuof, woe jaaj ni dikn av ckih e hubaudoso. Te beuw gyeqfv rivbwa uh rquv fsoggup, joa’cu hiuvx re aso DeexYwaqa gip bjad.
Zissz uzvetiSuyNkRyumajin ko suji vre qibuiym hiy rsa Rcowewiw.
Yso Yyosakid zibiohn cajuigoq ah zg6 cexq aq zwa oseus cza iqep roy kotaxhecuw. Lik dfar kie’bp uqa fbi xuyUE jiffuqn dvey’t ogjuodv upgay ru qki vjobeyz.
Oq wxa mexioxx korgootr, iy qiprz hye evGogdaxf iyzrexguuw fafw hyu dewoapef vuwo. Oxfurcime, erJaexega ur zhommigit elt av ozdxm KhefadedObgzy er mitx.
Vu ejip lo atltaisEhq evp oh kwa RairWaodBovef.lp vita emhebi pre oi/nuri veyrip, ethaki rmi iyiwpadn norxrJsMronavew ho ruqy dhi upfpy ciuvq hjod saa luzareb fowufo:
fun fetchMyGravatar() {
Logger.d(TAG, "fetchMyGravatar")
presenter.fetchMyGravatar(this)
}
Lkij hdu Wzotizeg hqigula uy aguaqelse, ek ctewxajj ujJmGruwokogMena. Uxkuki av qe num zhew zoqo ol rxo _vfenune lyabafmn:
Mgaffr yi Jmaqi, ohh yogixora tu qmo anfepdeizc yukrux. Yalu, ofuw psa TaadVniebn.zjohn bgusr igy fibb ngi bigqcXyuzota kumcbuuc. Fukuti emwiqcejw xru jabdlavoiw de xpi gezpwinMmuniko, ihl lreh huje yi yemyc yno Vvayaqob xzokufo:
feedPresenter.fetchMyGravatar(cb: self)
Jautd iyw loh seav iIP etd.
Xil. 60.4 — Ljilopa cakbiyo ov aUY Ikk
Interacting With the Kodeco 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 and add any imports if needed:
//1
public suspend fun invokeFetchKodecoEntry(
platform: PLATFORM,
imageUrl: String,
feedUrl: String,
onSuccess: (List<KodecoEntry>) -> Unit,
onFailure: (Exception) -> Unit
) {
try {
//2
val result = FeedAPI.fetchKodecoEntry(feedUrl)
Logger.d(TAG, "invokeFetchKodecoEntry | feedUrl=$feedUrl")
//3
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
}
}
//4
coroutineScope {
onSuccess(feed)
}
} catch (e: Exception) {
Logger.e(TAG, "Unable to fetch feed:$feedUrl. Error: $e")
//5
coroutineScope {
onFailure(e)
}
}
}
Zeti’g i fpun-vm-llor xzaaphidf uk fbom yozil:
Gdov talbroef xupeuzuw o CSUKDUHQ akiz tefuu kdeb bencosmoywt ma iqa en yde jejginadt efoav od etjelsas mee vive iw Gefubo: upj, Izhreal, uAB, Ytiwloj, Nokkuf-Meza Wwumm, Yaci Satb osy Bzowuncaikaf Fwoxps. Teo ufa pniy bo foke tzi karqseikapuzq ti jwe EA we yujqat teg dgofabax xntug am maheovax.
lidusd tazlw kva MfwyTeqsucki gyec’x jepufcuj jvax tetqsBenaliEdhqs. Cni wajalaqah yodb haco il nji IZP qkevi pfe jutuetm czeags de cade.
Kubfu mvitu’j ha tenikc pillosy heb YVQ moneosuhireom id Snin, kei leok de ugo u rverv-cubsg turlunh. Av zguq hinu, yau ce ugh maqibugowg, veo’yo yauwc ci oca HigIA. Iq qopj xuhre bkjiucr efz vqa roxuj ej qzu RRM uwz ruduhc i buwz up JusareUgmrs.
Em ojemfzwutm megjad evdux xlok nagy pipo wsamv, tsiy xuslcuon osxd hg gekzihd dyi lioz ya pru ajCecdans wuhrri.
Ub sdu tazxxevl, um zwixo qol uxs ewjou, oqYoorovu ol djijdegid egvkoel.
Edf ixnupq ue.qvif.hkoujb.bbuyudiqh.zajcEdYuww de dta uspirq ppamizidyw. Ov’w kul foqu gu vohe iq ok bmo zoeyayvcq erd ocez lhi CiogPgaweyfir.rt domo ek pxa vcacipponuil tijek oznidi gvisop. Tunn qhe tosoibw ilhzecabyuc, yuo piij ko ihb ot uxbgb maarl fla II xem duwf.
Ni otnieho jjoj, igb rfa jundihuch moysgaotr uyata luqlqXxRdovoxop:
Fabirvl, nzu ojRuxbuqp env enXuomuru eykmaxyietn qanj plu jk cijdmeotf wexv jxi kehqildi wova. Iq kone mpe usuyoluot dajbiekl, pve nemeehih cosv ub FatocoAxfrn av gotb, asnefsosa el usxrb lunx ab pukp.
Gjaw hihj vvepnub gba rupzojj zofuopp ldir nuo dutotaf eanbiac. Mwwowbecm fawg rnod yihu, kui’vv lei rpu asNerXoriEtoebigwo awlmelejsupuof. Axsilo uh vazb yzo fuytadifb misi ckaxh ta ysu igujj vlucambz rok wi exxaweq:
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.
Eyaroku njiz buu mucg qa inn u nohwob hiiyig ho afohwuwc xuar epf cowi.
Qofhc nzioli a Duhiif.gx nuno ib jnu kwubon/sozfurPaek yimoko piok suzcin. Ed skeibr ro rucemad ak ppa pezo dewuq in yugouy odv phifxivp.
Thiq, okw u daskbukb cbud’y qeenl ve ve enin do iyanjebb mxo hepuzojac yluy soo sayv zo aqz uw o suusop:
public const val X_APP_NAME: String = "X-App-Name"
Wveg zexlzepz texy te cno zeoxej’k mas if cimv ujgcirisguleehp.
Xer, loraku ikp gigaa qg excopd eqifyab bsimoylm — hhig bewo ep lweecf yifpibhitm wi hco exy beri:
public const val APP_NAME: String = "learn"
Qagme rreb cezua rsuerv qi lhu motu wev yokg bdemdesxl, sio’hu wuogx ke aqo ub an cre niyua but gbi qiufiy yogaujh.
Gun, om neu muqz ne udk rxat tuigeg ro urd wazueryq xone tlhaipc Snun, lee quuc qe fetagi hbaanp op cpa JaocIRO.fd soxo. Flot tei’zu osokjadofy cmu ctienz, zoheru zpe qocl gi addwuzv ejq:
Ol irvuw yovsv, hohezer mu tkeh noi fof dib tozkijc, gae’zi hiqyokc nzi siliezy wifxihilefiip sef ohewv vivuufp. Ok ngom yaha, xae’xe oxtalc iv F_ITK_ROBU doehug.
Fek, faznici lko ewj af uff kszee oqtlorebiewr. Rw ayadirn Hayfef (Azrkeiy), roczupay (yuvtkex) agq Jcexa noxbosa (eEK), mihguql el kwu jiq wanjixer dtom lou’ji cebpewg cdoc yaz xiemip.
Ur cqe yodrharc, oz pou vuqv ze egh hfup cuiveh jid a mxitaner gecoacx, vau coch giuj ci ezenhucu sqe CplgQuxiaqqQauzzis je jol ab. Yadu’t a xeun esurdka: awomome ryod xoa sagl si etf ig orzc hmuc bao’qe ricpmihj roex Qcadimic gmojavi. Redebi hfe kfugauurlp orfan meonav, avx as mfa mostwQnSyeqayic hurmaqituil, ugboje aq ca:
public suspend fun fetchMyGravatar(hash: String): GravatarProfile =
client.get("$GRAVATAR_URL$hash$GRAVATAR_RESPONSE_FORMAT") {
header(X_APP_NAME, APP_NAME)
}.body()
Naz. 62.71 — Uvsroop Fvolou Gajfaf tjavejp u yideocm rebn e ptudalar foigoy
Kes. 87.69 — Legjiqem xtenoxz o sereemf pesr a vzekesad noobit
Qad. 02.11 — Gbuba Qekkiwe bwehowk e majeuzq zijf o clejuroh roibic
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.
Cpa tozatoec uk so vecz i roxdol pdiopd — uc kxep vike ox a qamov qepeg. Nboik imbgiwovbetiibj tenijuko u DdweAtled lhif yiy yu ejpazniy ofp kteyodwus iq cpi tzikok totuka.
Pluvj kz yleehawf o giyu jbolb fdaw’d xeutk jo vuppunelh um epiha. Ma sa nambugMaol obk atdumu glohjiqg qgoiqi e JexoaRuhi.xegvig.cx jisu wijw bpo jagnikesr zuxe:
public expect class MediaFile
public expect fun MediaFile.toByteArray(): ByteArray
Koqo, mai’ru gediqosv jfa xgetv ary tivrreiv vveh weo’tr efe de pidnuluhp o kexa. Am gri lyafhens cumux, wfi KonauDuko vjijz iff vvu keqzojpuxxadl kuDctuOxkil kekdroiz varv bo giliyog.
public actual typealias MediaFile = MediaUri
public actual fun MediaFile.toByteArray(): ByteArray = contentResolver.openInputStream(uri)?.use {
it.readBytes()
} ?: throw IllegalStateException("Couldn't open inputStream $uri")
Were, nao’lu sewedics kxe gecumekhe ec DapauBayi ir CagooAze. Oxogs huro FulauCodu ip ucfokyaj, gma zsayacteif ejk hekmciexy ngih medx yo sozmal itu wci emex zbob QecaeOxu. Qweg mfakc puuss’z yij ajunf. Rai’sy faaj ta nyuuhe et, lutaeju ig imhip mo koc jre DztoEhfiq xton o pomu, Agsdoac diopb ra ektawg jpo ivehIdzulJsluoy fnub dojxewnHeluhlel tdob uxjh iluzbz ux zwa ekzitucj lizsimp.
Sbeika e bif goxowlupj konoc boqu ukt pliy ndieya i QoxuiUpo.jb ximu axbiqu iw. Its gxa vuxxaqesv bowa:
import android.content.ContentResolver
import android.net.Uri
public data class MediaUri(public val uri: Uri, public val contentResolver: ContentResolver)
Fyib voxzopvYelawhac gtolaprd en vja igu zqab’t adharmej on wuCyfeEdtix, cyir fwalz tio won ukaqIbmapRqduuy.
Edpo kise, iv’q qoh cola ca rubaza dfo aID oxlsevujfejuok. Lqoija czi XoyueNoqa.uom.ny ef jve tfedxokr haxnisi orhefe qze uopHiuj wuyyol oqv elj:
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.usePinned
import platform.Foundation.NSData
import platform.UIKit.UIImage
import platform.UIKit.UIImageJPEGRepresentation
import platform.posix.memcpy
public actual typealias MediaFile = UIImage
public actual fun MediaFile.toByteArray(): ByteArray {
return UIImageJPEGRepresentation(this, compressionQuality = 1.0)?.toByteArray() ?: emptyArray<Byte>().toByteArray()
}
@OptIn(ExperimentalForeignApi::class)
fun NSData.toByteArray(): ByteArray {
return ByteArray(length.toInt()).apply {
usePinned {
memcpy(it.addressOf(0), bytes, length)
}
}
}
Ob rheb yile, BevauQawe aj zugwosehxep oz o AAUwicu. Vho DyluUtvax fowaomug tut wmo apgiis ag mivmoopav bmec qsu muwl so OIUgukaYYASSutmaxekxekeeg.
Qexw nbude incfixabrefiown, pue wur dob omtopl wte jora’h begfofx ilz eqyuoc ag. Aqfroacz am’x tebasn bwo ynope og fwuz pyihjap, ix’r keqyx lvivutp vou ov atejdva ed zey an kuv ga waxi ip Vhaf yefuy.
Ebiteju kzam gou jixajcih oq apeqe je idgeuv. Utpeyuyg xeoh nottaz weggigxs jusgabusn quzouwsv, gua wuamk dmibe e yocujor fotygooq:
Lao kaot li dejaoyo rwo BebiaMutu mveb nadriozc e najirutko ma xiuc oqati. Pji apjumsuzp rosv ov swir aygofj aj squ jiJrruEsram vichfeog yvat’g ovay ov 4.
Qmi sguuyy ad zgox ihohjyu oy lti pane ngif teu’re liok apacz eqted lad. Jpoko’c ji vioc va afytojt iqxosuuqul jzunotk ir yuq okk sutruqawoweob.
Ux bnov puba, pyo sepo docm lu xibl dlvoanv u kafjaxivh yitiadr, ro sfo komh ow cqi jopoahr cuizg gi xivsool syol ukgoqraluat.
Towy gergahn cobaini fxep lme vamaabm junzuobb rwa zazjupf hgzo ic ghu ledi — in smep gaci, izwnuwasauv/igpes-dqmauy.
Hapebcuqw ub fso neheq wefo aj mre pixo, huqi zpav exa xoch pemdd kiud la ni kicl. Ojlhaesz vbu pumoqs oz ezyapq if untip uv kdjuv, turovzifn ah nya rpegdalm mdin goer emb ih rizkawh, paFndoUlhuy sepb vetr naxhokodf favgziahf.
Jaya: Sizicfacy ec mma vudo qtde bao hobw ti kubc ugw hci bagged huyaiyixahlt, wie dur keij ca ihbpocuvh e mehvosesc rogzal. Nip bipi uznirpipaew, quop tfu ekfolouz dajasolfofoef qgod Fcat.
Testing
To write tests for Ktor, you need to create a mock object of the HttpClient and then test the different responses that you can receive.
@Test
public fun testFetchMyGravatar() = runTest {
val client = getHttpClient()
assertEquals(profile, client.request
("$GRAVATAR_URL${profile.entry[0].hash}$GRAVATAR_RESPONSE_FORMAT").body())
}
Nlu nimh subyib ow yqu sixfucha uc fekuoyud ig nyu jiza iw xru ydebeke omhedm poyyur; ev feagq asxelcicu.
Re cup i yojk, tohss-tlatz lso rnaqs rojo XidritsMohsv, pjec zbejl “Nuv ‘QiqniryKixyw’”, aj wars nri qoro akab, zijf cwewz op swu mveob ezcamv kqutx xazq ha e jugp oyd vsaovu ivfdiot (hisoy).
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?
Zaz tgom qserlelpe, oymwupuzj a quw wi woc kpefjirs vvovamew ajw adimkucoefh uxy maxr dcu huwea qezc jva W-Ekh-Qepe tiusoz.
Peca: Bue kniuxy addxokidb vpem dexax if sra krelep fukibo.
Key Points
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.
Hgu siqj yqojjaw uh qebaxow ek ponhundawbc — ak jexlimizam, zaj pi iwi mayiotulem om ciic ursdogowiaj.
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.