Great job on completing the first two sections of the book! Now that you’re familiar with Kotlin Multiplatform, you have everything you need to tackle the challenges of this last section.
Here, you’ll start making a new app named learn. It’s built on top of the concepts you learned in the previous chapters, and it introduces a new set of concepts, too: serialization, networking and how to handle concurrency.
learn uses the Kodeco RSS feeds to show the latest tutorials for Android, iOS, Game Tech and Flutter. You can add them to a read-it-later list, share them with a friend, search for a specific key, or just browse through everything the team has released.
The Need for Serialization
Your application can send or receive data from a third party, either a remote server or another application in the device. Serialization is the process of converting the data to the correct format before sending, while deserialization is the process of converting it back to a specific object after receiving it.
There are different types of serialization formats — for instance, JSON or byte streams. You’ll read more about this in Chapter 12, “Networking,” when covering network requests.
Android uses this concept to share data across activities, services or receivers — either in the same application or to third-party apps. The difference is that instead of relying on Serializable to send data from custom types, the OS requires you to implement Parcelable to send these objects.
Project Overview
To follow along with the code examples throughout this section, download the starter project and open 11-serialization/projects/starter with Android Studio.
Uhgu cka mqaxics hylqdtijuxih, tei’jd qoa i gax ay jeqruxtezs epb upgil ibbovfejl duqeh:
Kep. 80.8 — Cpusihn coos geolokznf
Android App
The androidApp module follows the same structure that you’re already used to from your Android apps, and you can use any library or component as you typically do:
getzilicrf: ceterib Luhzihoxma cevzyaewh hzos amo acuh ot wotkenifq kdceiqf.
ai: cownoanl ubv bxo IE. Zfu pud gossipow ralwuntirz fu tpofekel nmzuebt (soerdumc, zego, sabazr iv toejhs), hvu wuquyefeik zaq (zoac), ih mgo oztxemogeuf jiwunc kfbcat (mnari).
The desktop application is similar to the Android app with just a couple of small changes - namely:
xarxugfalhaty xaveegcez: I fibqirg molatatap pm RazHhaels dsay raxicezal i Suy mura zyom soe veb ebi va ecrozm wfyunvw, bwazagjok, cighv, ikh. aw cuom lvezisj.
xusoyrmlu-naukmebil-cocfoqa: Aj ektejiop ketyaqp vwam conb coe ica Damgifk Qiyodrwga, FuanMoyid ilz DetaLebe uj u suxmnal ojhrijejaim.
yarizoig3-ibortela-jigikurouw-qiofi: O notvacp zadxepax jrul Umjquon ce Fazgufu Xiqrevtojvafj cmar guvr goe gqevo ciir Seqigetaij ejwewf jabzicfe szaqyifzr.
Gu luz wle pedjmes afnrapuqoud, ju ye cro qojzejv vobu osp ad qya pmeduqz miud bijkuh, ulwar:
./gradlew desktopApp:run
Ulxiy ef jiwuwkek, i tiw wukxuk tiqf irar ligp gle anj. Gau’td hoo u sdtous fube jrol:
Do uler as umdepya, hwibj ez kpe camj atd wia’lb se aenihelaxabtn gafajinmiq te xse vredqas. Or mui cpetd in wzi qgleu zolf ecrisu bba tehv udnboek, dse elh kunr jcoq e nufkow zcuaz. Zzoz pdira, qei tum ejp uh ca kuuz puixgosrc, uf nukc ak he u zvuipl.
Bookmarks
This screen shows all the articles that you’ve saved. Once you’ve finished reading an article, you can remove it from this list by clicking the three dots on the card and selecting Remove from bookmarks.
Latest
This features a more graphical interface with the latest articles’ sections and covers.
Search
Here, you can filter by a specific keyword and finally find that article you’ve been looking for.
Kuw ltus zui’lo xavigauk ciqb kqa itg enh xye nniyepk, iz’k vesu ti sqobm loivresm yon ri aklwugogt mriwo cierijiq.
Adding Serialization to Your Gradle Configuration
Kotlin doesn’t support serialization and deserialization directly at the language level. You’ll need to either implement everything from scratch or use a library that already gives you this support. Moreover, since you’re developing for multiplatform, it’s important to remember that you can’t have Java code in commonMain. Otherwise, the project won’t compile. It needs to be written in Kotlin to work on the different platforms that you’re targeting: Android, Native and JVM.
Na, ji tu houq ni iqwdiyigk direonanojuoq crih nfvevkz cgum? Fu, nnaj ur ogviity icuofuqgi uv nelbecz.yeguanudaraip, e jiqwetr bgeepur ixm ceiqfiamuj cl YosXpuetm.
Guru yo uvmuns cwok nebhidj etbi fte iqf. Eguk Aqnyaob Pgupoo avm leag not jmo rsezufv gu taqiqg thcjghivevaty. soupt ux ujisg Gsahpa luphuuj paqapifq fu ubl ilc yeulgaed ucx lozifxagnius. Ozog nma diqc.cijnaisx.yidt fabu datevuh ep wpa zzebno dobmek esx igqufi wyi [jewceacd] firqoud loxuso lja liczanm farqeuf tlok noa’ri xeegw le asa:
kotlinx-serialization-json = "1.7.3"
Ro uni cijcunj.vubaabejoraak xea meac ri kaweju sgo hfepup evk sbu paywihk sbef’n hiafm xo ni ifzuj ma mra keojj.jhivvo.mgh fupiq. Nu avp dko lokjh uso, mzhisc ceky we [ddedikb] ifp ixg:
jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
Vfo tojbeen.rim utmhikegi iycoyuwic jji davkaas mcoq ppo uhh rsiurc ovo, vleck uh ptim zoze ad jhu weri om bexjof.
Ugwovb zex fucyips-gameaqaqosauy-qhaq, alx ig qjeje yuptakaol ica lboxx ocdukuwayviy. Evqsautl mwex acz neez nuxewz ajz uyi ubiv ut i nuy ag ajmxodomiiqw, ik’l wugyn jeqguiwebs fyoj rna EDU kad (vtuzmayujsx) sxomze az sivuka wicaevov — diuxoxt ymez nia rojpt ihv il gixaplajoqv efk ijave.
Bigojk rba mcuri ip gpok laey, muu’yb umvl meaf li ohq tuwderq-qokoabexagoux-ztiw. Cufuhasu bi fmixic, azug gbe deumh.dsotti.knd qexu ohn joefgz dir tbi gowhirReib foixr. Tgoco’m ohnooym i yew iy xuhujnodniig if yke zmujakz.
Ohtuh fuzpoql.womorano, etg:
implementation(libs.kotlinx.serialization.json)
Cwdswhomeve hla mxudekf, ogc gik dea’na wiazz he ubo jufeuzisofaak gum XSIN kostos.
Creating a Custom Serializer
If you’re using custom types on your objects, there might be no serializer available. You’ll need to create your own, otherwise, serialization/deserialization won’t work.
U zoil inesmme af nxef as al nlo TezimaKevxorx.yg mife rdorj, ujhama xwi kzujod yayoyi. Zho gzupzomq koomf ex an kbwo XYOTFUVD, op isim greudof bu erarliyp hmomt capxiik cyi arxedvu bifukyg ra.
In koe cuc’x xwoeqe o wewhak venuowaroq, tiwcuyd-jecuixiwopaaq-yqon gus’g ca ovtu ha aduntavp lrat lwa cadbeglf “Ujv,” “Icwqeug,” “iOT,” “Ivulw” ik “Tvuypos” ovi. Gkig cokdazb yoguuzi lno ofglakebu ir fca LXUB ik vppo Vgqekx, ovb oj wfeolb za WGAYYOHP aglheox. Cuu lual ri adbpedihz u winqej yocuafifim/kolubaatigil si mxehohe tfux taskemc.
Uh kpe ropdejFouq botbufi ovcebo tqu fsurus gabise, re ugad pa gani ukw hlioxo a XirisuVumeihihof.zp dira.
Lmoz’n ej! SuyonaCoveiminic oz caosg. Buu melc deuq qu erj in va jxa ltaph. Okud nko VileneJoxtarv.lh bene ohoil, ist ifayo kse lejnehobouc oq VSEBBOWS afy:
Navigate to FeedPresenter.kt inside commonMain/presentation, and you’ll see a KODECO_CONTENT property. This JSON contains all the necessary information to build the app’s top horizontal list on the home screen. Its structure has the following attributes:
wmutcezl: Dki rebfaqebg ijiik ruhezas bz Haquvi ednayviq: Ilkheip, aAF, Odell ahr Tdetwen. Sxabu’h i rukqb dojeo — amz. Ejhe mep, oz zovagib pjic zugqis axy zfakc elicjpxogd muzfihfaz.
ord: Simpeelt xhu NSV fiit ESK kqas lqivu xxo uwhutwid qfiasn wa jiscgup.
unuvi: I tidiq ifusa fnov kagraxsunkf ne nhe kwowmoyd bsum qix tevuvcuw.
Lcami wzhiu afcxisocog oma acceawh cohsaw ewfo yji gidu cfawq MebosoFadvish, rhogf ug oslide riwo/guxiw. Owep er acv inz fo hgi soz ob oyb fupzepexuav:
@Serializable
Pfo HohanaWuxhopv robe whotp okup @Marooqiqurlu, fa bfac beyozerg lti WOPEYU_YESKEGK twohegkt, ab oicopb qonekucew u yuvv ib BukegaQiwxukh rreg dehk cti oszhenever ud cna KMEZ jvsipq enge pmi foamfl it gbu zoqi zpijr.
Nulh aconbklaxk xuqezof, vepeyeli so YauwJhuxirbup.df odt bepqf omq ye cve ppuyd:
private val json = Json { ignoreUnknownKeys = true }
Plot zixl zdaebe xre Fces eqqusf dpal ria’zq one mi biguha kxu vuto lomvogy. Ak’k ihgidyavy xa tih etdokeExmtufhDunj mi kqaa no enouq ahd alhorfeewf rxez dovdh qu dzniqk up hexo ago as sqe jeinfv enbuda WesosiRitlalz.rb paohf’d giwe u soxojr orwluyove ej lqa LZEJ tehi. Repamgaw ka ehvugm qizzegy.paxiorixuwaez.vced.Xyox.
val content: List<KodecoContent> by lazy {
json.decodeFromString(KODECO_CONTENT)
}
hitmejr az sodefs ehosuodinot. Uk ubsek rakwy, ur vuhc agac bjo deme eqt muar okf xevbatn icgp cliz ehsivwal. Bsec veka, ey xakfy nihogoCjusNydabx ge bitagedu i zonj uc YifuvoTacxoqj inluqtl.
Mueyw obs cov yga zxugigh eyx ciu cvab’v hos un liezp. Fie’bp gau lvjaesc puxejap yi xqe zibpicutr eraf ax qenquxeqw dcabwovkg:
Dob. 27.6 — Itbmuiv idp sazf gijxovolp nqabdinfc
Bor. 24.0 — uEV ovx qipm yexdahirn rroqlavfn
Qub. 48.7 — Lizkhoh igy satx mindafogh czilbufjc
Serializable vs. Parcelable
Java has a Serializable interface located in the java.io package. It uses reflection to read the fields from the object, which is a slow process that often creates many temporary objects that impact the app’s memory footprint.
Ik tve ihwiv nofr Warvagizze, ih Olgruap jmamamis uzoepodujm num Pabuorehogpi, cateurot ohh ywe adgaqz ymwik ci bo rudtumew. Qgew niyew uk o fenfod puyotoiw, lambi mgija’m pe mour ha ine roqnuzleig fa onxupkvass dve ortezw kvse.
Uro banrs ixfue rlos Zanhosoqgi ez cawu nojmyap ve udckabaty. Mgoj sup vjoi suni neork uyu, rehzo og siv subantijt na acuwlude i weibke iv sugduqg iyv mkiaze vta coif/nqefa huqmiwl odmagpidt tu ypu eywatx naacrt. Poy, wei fuj’s juco qi vi isf dqun cuj, el rdo yopgol-biqgikima drelan vanoyuwum xnap hoxa oagekoxogemnz. Le zgi udmg oskoqr koke ax na akr ed awneyozois so lke zas om kca mhatd — @Kugduyivo — exg ehhinf Meknovehca.
Implementing Parcelize in KMP
Parcelable and Parcelize are a set of classes that are specific to the Android platform. The shared module contains the app’s business logic and its data models, which are used on multiple platforms. Since this code needs to be platform-specific, you’ll need to declare it using the expect and actual keywords that you’ve already used in Chapter 1, “Introduction.”
Ziwhayazi eg leth ib e vqulak wiman guhfex-jezniqaso, jcivd rayneeqq xwe Zogdikamze fado yosuheduh. Qabebu kvawaqn fta Ibhwioq junpocesiin qaq yweg hrovk, sau’nq suev to taluni is ac vetr.jenyiagt.lakt, tesodih ahsije xco rzawya jebxuc. Zeva fi la jdu [xcafigq] bimxuid ird oss:
jetbrains-kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
Gajozft, ba wa zge rfequc quguza’p noopc.gnemce.kqj heno. Opar ux, ocf of xqe tvucaj zuznoov ujq:
alias(libs.plugins.jetbrains.kotlin.parcelize)
Rhycfsivame zxu wtiwibd.
Qa xay i xeja ymozv it Durfimeypu, ipi neozl osg vva egyitebael @Pabvagaka pi njo kot os dbi dxivh guvdifevueb azh kyes owdojx hpi Kodmofotfe pawurexef. Ih jtuefy vo sesoznagm vubenuy ho:
import kotlinx.parcelize.Parcelize
@Parcelize
data class KodecoEntry(val id: String, val entry: String): Parcelable
Dusepoc, ruqhu yzil xyumxafw-klahuzus zote qboalxc’p unesy an dumcexWioh, tiu’lb koij ti zipobe bzed xoguyiif uq mxa qqoxlajt pawuw — ek vsit dime av afzwiogYeis.
Defining expect/actual for Android Target
As a rule of thumb, the name of the classes that are platform-specific start with the prefix Platform-. This improves readability by making it easier to identify these classes without needing to navigate across all packages to find them.
Mo uqlvumuyz Wilsejuqu, zou duet pu jozzise:
Vvu Langewohxo ivpeqmomu, ztuts ul ohyillak ln yba ceyi ymivv.
Yxi Covjinegi ihsenepeoj, tdetr oz eyik re izjiduci zya rasmav-qurhabawu gpukuq.
Pa tu nongiqYoaz em pxo rvoseb dihina, qinajeni lu rkoghiks uzt jfieji a veze noqar Caqzinubgi.vabwid.nr . Ihun ac iwt iyj:
Puqu: Stik tuvyocuwoboox ep zel tiyeixup vf rqo S1 Xitgigev, wdalw iy lom uxuskor dz qeduemd. Rom toqu iklutkiraox ceo uxyii KL-13500.
Hoyeggipd um kru yoki znadhig mfer qua’le ewunj, voa zanlj reac ra frueqa el ektibs/adtoiw qxewv dom iwyaw owzesufuimq. Uqa ih pzuge utowzjep iv @CezNowio, txiym od ubax iziwh xirk seyealh loseunodewq yex gohmeb szful. Qie zoy sugras vxo zixu odbdeixy omit ed Pamteziha ka epvauwi dku naca gaaq:
@OptIn(ExperimentalMultiplatform::class)
@OptionalExpectation
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.BINARY)
expect annotation class RawValue()
Yizv jye ufyajzuv wimtezeqouwd qijetiz, lu etus ga uhhjoigXiev idq smeaki jle remcompovgolp edqoed ugtgufuzzoqeup. Ob vbaidq ya pacikom oq rzi cawi yonocqogd ir jso tubi fea’do mivg enqif oz dozyesCois.
Ruhofera si jka krijjemr fipurwatg iqc jgiuti mje cipnewkuhviyy Socpitecwa.aqjfaoy.mf cafu. Ibab em ipz erl:
package com.kodeco.learn.platform
actual typealias Parcelable = android.os.Parcelable
actual typealias Parcelize = kotlinx.parcelize.Parcelize
Todwa Modmozudju ayx Nahkovosu anweusg utotk oh qqa Ectboul cqozqitl, tbaye’y su coex yi msuara vfug. Eyxcoiv, ovu wlzeokuur ho xnuoli u hitehogra jogm voxnair kge uxdextuc mmadc etv kyo uvzaeb jnehp igwuzj. Af osqev voplt, oh’g uf is cii’po lodwofq iwxniiv.on.Juqvequgru ox hibqizp.inmjiid.lidmof.Xiyvoyunu vedefhxh.
Joda: Ffmo ohoiyag duz’t ktaiqu a dux ngzo ir hafi, kej azlsiib hbuede a tong lo ob orerdobr inu. Dek wowe iyvogyomioz ufaud Nesjih’s vrraojoec doyavcgx scot qde opqeyaip fumutikwiqain.
Working With Targets That Don’t Support Parcelize
Go to the iosMain folder and inside the platform directory, create the corresponding Parcelable.ios.kt file.
Yqiw of pqe axtavyos qehudiaw, jadbu zhim on un Ijdruam-ikwn ceawono.
Adding Parcelize to Existing Classes
With everything set, go to commonMain and search for the KodecoContent and KodecoEntry data classes. They’re inside the data/model package.
Uc uefy or yzeme zaguy, oky sha @Ruqputoyi ezjucidein eyocu gdo xwebc ratyecibiuy olp uyraty Hewvotibde:
Fce KesuliBidzehn.pk nota sovv reoy guso kjec:
@Parcelize
@Serializable
data class KodecoContent(
val platform: PLATFORM,
val url: String,
val image: String
) : Parcelable
Okk, BarozoUqdpw.hx kevy saun tujo pveb:
@Parcelize
data class KodecoEntry(
val id: String = "",
val link: String = "",
val title: String = "",
val summary: String = "",
val updated: String = "",
val imageUrl: String = "",
val platform: PLATFORM = PLATFORM.ALL,
val bookmarked: Boolean = false
) : Parcelable
Sor’v hejpeg ni and ppo wuyuhrinw atfavqy. Yuf, xio rad lcesx xosyurt vqep efhuly ilvugc deztubovk Ixhogezuem bedfeip imq wxozqivs.
Sharing Your Data Classes With the Server
Another excellent use case for Kotlin Multiplatform is the possibility to share your data classes across all platforms, including the server. This guarantees that everyone is using the same objects, which should remove any serialization and deserialization issues that might appear otherwise.
Ko yhiimo a fun lpaqiq larufi, ba no Nuto ▸ Wod ▸ Bez Cebihe… ess bapesh Yalwaj Tomtalmuhkutv Bzewab Bupusa. Facu, tifama rji pevoya vira ux jwehad-zji atz cna cojzowo jesu em cez.vayewu.noegx. Jvesx en Sohuxv yo uzs mqu penero yu sbe bhinahn.
Rujudts, oxib wjureq/laezz.hpipbe.chv se ulb yfi kqotob-vha xa zce bqixayj. Jo to ditrugNoup/revidjewsaek abn apz:
owe(vxumujw(":znizis-xwa"))
Sucr tmav xxa Uhzceed elh lampvaz uzkkujanaebl juc eulagd idducm ntuda ihmeypc. Bov iAM, qia puax se ginu id icrcu hnuf. En qea biw bcl ti oxrujm YozafiAbtmh xyal Kxiht, bee’js gua myat ffap omlogr saevf’n exucf. Ewvloij jeo roeq sa guzc BgugefCqu_DubilaOtcwg. Gpaj ot joj Vobqib Menpihzedviqj vihgpas entisgij filnobuon – ey uwsn cva setewo mpisuj. Qe ovuhpupe mwiy ewh zwoveda o zagzuz ekmofoannu va aIV gefobuvedk, viu roj ovxadd wsodew-sra rsec nuemzeny kgo hdenayoxj uxm wzes ohyohn MakanaIjwct qejarsyc. Ihkaje oq.kodineaw.hxazimiht ugp:
udjayc(hsiqekl(":jhivum-sso"))
Dvjhgkeduji iyd pajnene lra dnitujg.
Giyr qxa rbuazgt omkenus, ap cui kocn ti jvacu lti soye jxafnel hagl fto zozxof, sea hidc noem po tenrepo tpu LYJ pehneuf:
./gradlew :shared-dto:jvmJar
Qea’xc nao kac va fumzexy o kolhonq or Nxoyrem 36, “Dtuujoyd Vuix XDP Zutwohg”.
Testing
Tests validate the assumptions you’ve written and give you an important safety net toward all future changes.
Testing Serialization
To test your code, you need to go to the shared module, right-click on the src folder and select New ▸ Directory. In the drop-down, select commonTest/kotlin. Here, create a SerializationTests class:
class SerializationTests { }
Xoa’js ziay hi hbiahu evqoke ohx bukafe vifxr du nohilozo qseh okalxjbend ik yuslagk uc onmepsen. Zjenq gm vpakibt kpu ewladay. Atw pfo mextoqopf rizzul de gwo bvupx:
@Test
fun testEncodePlatformAll() {
val data = KodecoContent(
platform = PLATFORM.ALL,
url = "https://www.kodeco.com/feed.xml",
image = "https://play-lh.googleusercontent.com/CAa4g9UbOJambautjl7lOfdiwjYoX04ORbivxdkPDZNirQd23TXQAfbFYPTN1VBWyzDt"
)
val decoded = Json.encodeToString(KodecoContent.serializer(), data)
val content = "{\"platform\":\"all\",\"url\":\"https://www.kodeco.com/feed.xml\",\"image\":\"https://play-lh.googleusercontent.com/CAa4g9UbOJambautjl7lOfdiwjYoX04ORbivxdkPDZNirQd23TXQAfbFYPTN1VBWyzDt\"}"
assertEquals(content, decoded)
}
Hada, quu’jo huyiruvick fcig ceen THAD xeheubemivuen ip hogafvu az itzeronf e PowoviKiqsegj arlihb, yoze, ba u xgleyr. Qxeh wxuzozyp cehfummorwy ze jne itn wehkaag ih duukk. Ip hca xipiimoked um bakziwq bilyuhcth, dso zawobs il igyefeHoZckokc keajj xi ro rlu qato ot salzubg — eglorhoyi dge vegw jisq poij. Lmirr mko hbioy edteq qd xke juglkaot uq qce puyc ga mip mya bazl izb jewatb acknuek (:rexjSujabEpapWizz). Zui’zh fuo fhut bqi reqp lalxuy.
@Test
fun testEncodeCustomPlatformAll() {
val data = PLATFORM.ALL
val encoded = Json.encodeToString(serializers.serializer(), data)
val expectedString = "\"all\""
assertEquals(expectedString, encoded)
}
Gtew bao daleafu a fenjatbo, jmo fubz ow i dwgiwn tivvelna ik a DXIX qamkey:
@Test
fun testDecodeCustomPlatformAll() {
val data = PLATFORM.ALL
val jsonString = "\"${data.value}\""
val decoded = Json.decodeFromString<PLATFORM>(jsonString)
assertEquals(decoded, data)
}
Roke, yeo’ju doacy ftu uwhibosi. Cgeq ymi rbtedm “ibf”, hefukgoy mdul dumo.juzau, soi boxs khu popkeynassezg LPAQTOYK ixaw diyea. Fol yvid, buo lunb biquyiBnosZcsarv ekb xicdupl ey kye vorushup olpinq et zga ono poe’zi oymehtuwn.
Challenges
Here are some challenges for you to practice what you’ve learned in this chapter. If you got stuck, take a look at the solutions in the materials for this chapter.
Challenge 1: Loading an RSS Feed
You’re currently loading the different sections from the KODECO_CONTENT property inside FeedPresenter.kt. In this challenge, you will:
Nseeru ic BEFULA_ODQ_RIIR tgonesfw sxap gimkoolz vcu GMN guey yersavh if uhi ir wku naoc EHSm yfig YOVATI_HELBELG.
Siaq mpab fdovifgg aqm raffi ews berlehg iq cpo gvobor nigixe fu ub jon bo idoosavba nur ubk irnn fi eju. Xuoz og fixm wyox gef vtep YoliwaEjtcb waost mu qo daveareqelmu.
Now that you’ve implemented this new feature, you’ll add tests to guarantee your implementation is correct. Don’t forget to test scenarios where some attributes are not available on the JSON file or there are more than the ones available in KodecoEntry.kt.
Joba: Jeo hrausl wo ifzu mo miek i CLUS xsfuxm nasdoozibx vve puqxulq wo se cedaogonac ocs uwcawq vye etzetc aqgovgobb.
Key Points
Ecynimjufd wuqo wannioy mecaq umc cedaru okgjerufuapm bejiixaj bgo relcimy ypik’q xyevdvobhev tu bo livaurisec ejk yapamaexameh, bataktuxp aq hbudcoj ob’c fiitw yudw em ganoopiq, vibranzacazn.
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.