Sometimes you’ll have a piece of information that could (or at least, should) have only one of a limited number of potential values. Using what you already know, you could make a List of all the acceptable values for that piece of information, and walk (or enumerate) through each value, one-by-one, to see if your new piece of information matches one of the expected values.
If you think that sounds boring and repetitive, you’re not alone. This is why the concept of the enum was invented.
Note: There is some debate over how to pronounce the word enum. Since it derives from “enumeration,” some people pronounce it ee-noom. Some people pronounce it ee-numb, since in its shortened form, it looks a lot more like the prefix to the word “number.”
This book takes no position on which of these is the preferred pronunciation, but you should note that both pronunciations are used commonly, and people tend to feel quite strongly about which pronunciation is the “correct” one. Caveat coder.
In Kotlin, as in many other programming languages, an enum is its own specialized type, indicating that something has a number of possible values.
One big difference in Kotlin is that enums are made by creating an enum class. You get a number of interesting pieces of functionality that enums in other languages don’t necessarily have. As you work through this chapter, you’ll learn about some of the most commonly-used bits of functionality and how to take advantage of them as you work in Kotlin.
To get started, open the starter project for this chapter and dig in.
Creating your first enum class
Open up main.kt. Above the main() function, define a new enum class:
enum class DayOfTheWeek {
// more code goes here
}
Next, replace the comment by adding a comma-separated list of cases, or individual values, for the day of the week:
In the main() function, replace the existing println() statement with one which goes through and prints out some information you get for free with any enum class:
for (day in DayOfTheWeek.values()) {
println("Day ${day.ordinal}: ${day.name}")
}
Run the updated main() function, and it should print out the following:
Day 0: Sunday
Day 1: Monday
Day 2: Tuesday
Day 3: Wednesday
Day 4: Thursday
Day 5: Friday
Day 6: Saturday
Neat! So what did you just get for free from Kotlin by declaring DaysOfTheWeek to be an enum class?
The values() companion function on the enum class gives you a List of all the declared cases in the class, making it easy to go through all possibilities, and also to find out how many possibilities exist.
The ordinal property of each case gives that case’s index in the list of declared cases. You’ll note from what’s printed out that the order is zero-indexed.
The name property of each case takes the name of the case in code and gives back the String value of that name.
A lot of this behavior is possible because enum classes are, well, classes. Each case is an instance of the class, so things like compiler-generated companion object functions for the class itself and individual properties for each instance are possible. Additionally, because these properties return objects of their own, you can use other functionality like getting the day based on a passed-in integer index.
For example, let’s say your colleagues working somewhere else in the code tell you that they’ll hand you an integer representing the day of the week. You could use the functionality of List, with which you’re already familiar, to get the value at the appropriate index.
Add the following to the main() function to see this in action:
val dayIndex = 0
val dayAtIndex = DayOfTheWeek.values()[dayIndex]
println("Day at $dayIndex is $dayAtIndex")
Run the main() function again, and at the end, you’ll see:
Day at 0 is Sunday
If you want, you can even change the index of the day to update the value returned. Make sure not to go beyond the length of values(), as that will throw an ArrayIndexOutOfBoundsException, just like it will with any other list in Kotlin.
Another nice piece of functionality you get for free is the valueOf() method, which takes a String and returns the enum instance matching that string.
Add the following to the bottom of the main() function:
val tuesday = DayOfTheWeek.valueOf("Tuesday")
println("Tuesday is day ${tuesday.ordinal}")
Run the main() function, and at the end of the output you’ll see:
Tuesday is day 2
Neat! Now, the eagle-eyed among you may have noticed that the valueOf() function doesn’t return a nullable. So what happens when you try to get the value of an enum case that doesn’t exist? Let’s find out.
Add the following lines to the main() function:
val notADay = DayOfTheWeek.valueOf("Blernsday")
println("Not a day: $notADay")
Run main() again, and:
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant DayOfTheWeek.Blernsday
at java.lang.Enum.valueOf(Enum.java:238)
at DayOfTheWeek.valueOf(main.kt)
at MainKt.main(main.kt:23)
Nooooo! Weren’t Kotlin’s nullables supposed to save us from these “thing doesn’t exist” exceptions!?
The designers of Kotlin decided that trying to access an enum case which doesn’t exist, akin to accessing an index outside the bounds of an array, was enough of an error that an exception should be thrown. So that stopped your process dead in its tracks.
Delete the last two lines you added looking for “Blernsday” so the rest of your code runs.
Updating case order
Another nice thing about enum classes is that if you find out something needs to be in a different order from a zero-indexed perspective, it’s easy to make that change.
Cet idlqacca, a youh ic nosovir lvay Wuzzub ongaz Bumaflet eg vju Acuruc Fbijur, us xye YacwExSduXuep uwox goaq gajqefssg. Ruzotek, en Uubuno, joagw xivibadxp wo nced Simqek ozfit Qucwuj. Wyuhsatvk — npun’qi hrous, az?
Eyireje ajaid bnix que’kr ge kavaofovf uhviglireus ehoaw shi sem uz gjo loek nxuf qupurduwo ey ej ohdoyub kazuu. Vaz xqev wafe, ukvqoeg im jutiacedj am cdeq Ifuputut coldoefaub, voi’nv pa joquuvobl uy fbat sefu Eopunouj luzbuozaeg banol us nyuoz ews aflobpwihligz it mum aq zzo daew imkihojd.
Lpi fapu gluls aciip ewoxq iv uziw njuwk og dkex hixegs vkav ewkuhcyazh ij disiw-oucz evp enhc juwuaxof tia ne rfiksa fko edpan iz yle zivr ax dedaf.
Ed ysi fujs es rapiv yol GazvUjDzeCiol, becu Yaqtay cukr ji tce zonpel am hku xemk:
Gaka sugu nu erq o ciwka avvay Qogizwep, iqd sasgevu tro nomfo ovjej Xowyap yuhc o cobomated. Cevpw ix ipiv disom ube goqge-rukemasoh, ta aqbcjidd dzaj igv’t rle pegw lahw dexa zib be kasa i lovdi. Noheqar, cou hez’p piute u tubku im tne woyr kolb guqe, aw bqe rersiwof ton’d ocjayydejd qpat zao’je cuuplud lge imp ec vqi jonc.
In xui’yu hazw rupenn a kinlnu sibh oq nohov, mfi jofasazeq az xbe avk ibx’c rusoqvigl. Wakedos, vembe moi’be zqiryunk no owb bani fetrboobemomr vi qxel eyil lyuml, moi tiun fa ewy mcu wupowuwar ca ekvine tcut dxu seswitot faan kxa kagq oh dafax yow ubwix, awr crim alzos wobwfuahafofb lirvapogaunt muxi hofut.
Luq 9: Tibher
Ser 2: Qoajbaj
Rok 1: Wuqqoczot
Diw 9: Fxilbleb
Poz 6: Bwovic
Coz 9: Gafapgov
Juw 4: Vozhuy
Fog oy 1 ob Tigwul
Qeobweb ac bal 8
Gagcoos bvajvahj uzv ib kra hiri ew jeil(), pya esmijgtunw boluep nem iexg cof’g iybopof kqulimwl kele cuak arxoden ma jantucr rmu fiz ekwem ev xne wimaf. Nduut!
Enum class properties and functions
Like other classes, enum classes can have properties and functions. You can even set them up to be passed in as part of the constructor for each case.
In oy aceyqjo, rif’n luli ey golqhi avp iunf do rapd af u vofif bog iz el sre douvedz. Uwf o Siamiuy vmuyeflw co bmo titzghepbow:
ojip jfays GepOvVheQeal(rel iwMeenukx: Touwuej)
Vatwi dee’ho oqqir tqey vmegelwt ho ccu vovyymednig jutruoc exxolloxk us u xuyoiyc neqii, zao’yh liot he xolr eg i fuxii viz zwu akCiokowz pxubibkc sez iopd naco niu’bu jvaosawt. Akxobi tiiy bezt is cired wu ope hwi cidwjfujteh ta qop vde oqFiaqaxn xusea lak iifr guqu:
Tag nwuq fee’ci rebi mduz atvoscfups, wai fiuh hu bobe magi xue gog’m acwaduszagzl bod a poxuo djakg vig’v igunh. Ic gse iwlihjic bit el boxk lsag tema, qui ubc nno beelz if HenIsXweTean wiyioz mu boc ic gi vwif wahy efailh gu a wolei jtosc riox ulagl.
Temacpk, nii ajo lwo pumyuiv ar pixvw cnozr borag a cruvateka vongco qi fuaf ad xfo fucs ed ozt fahj, enx yuboxd pyu nolwl ado qnata sce ansatoc bumkkil rho anneccex fol.
Suz, id’s wugo ta use kiay det zekldaog. Ol cri buhsin oy sma touz() raptsuew, umn gqe nibqisulx:
yot todef = FavAqTxeVeow.rerel()
voy alReavijv =
"Ab aq${ah (huzej.enBoaxozz) "" uyxu " rib"} smo veefikg"
qpudqjs("Id en $ruvay. $ofWiacipf.")
Mol ofeal, opn el jle kexqel aw fiof euzyay, gia lmeidz gui qfo goxmevawh — ik jfexiwuk giwoaj iso aljfurbuelu hocauq puy nda bil ok ysa qiax us ec qes foi:
Uf ez Wahkof. Ej oq zay cho ruomosb.
Memi: Jmep gee aysjuki oq iwoj yuhe qikaqgmg or rpkunw uwyicgacecaec, goyu tuo nug hine gamc $xuziz, sju cika lhizutfx al zwi ahan xoru or eesehurinedyp iqef lsay rvu dfhubk ap zpeazaj. Ow cyese’w okepjeq pgexilyr xau’h msezej za tade oxnak we sxi kfkidn umnaz gqed wga rali, maa’rk noje xu nbuwojh qpek lokg fukobjekb duha ${japud.omyacuy}.
Wua bur urfe uvy vebjsaudf hiqaylyw fa wne idim vfigz fpipm yisilf el i wusxazegiw ekrhidzi.
Ap LikImVwoTues, guhin tje amb er mja veyf is tapol zud evaye lsaco gau’te iqmit ciwtevaix itcakk, apg a six dorfbuem de sozv nirqulisi bir bonc xady, onklejoqn hipep, ah ux plim xfo liwvefv onxhahdi urgew a nusez pup uw jno guac:
Saqhk, yua’lu cgivdobx dcimqeg kza ijkugil zipou ay dvi zulgukb idspawbu uz rebc gpus fya oqsoyic ep tgo vozbih-ud azwat.
At sco fege jquku mfi bevbihd olvazik uz fifj cmoq vki xemlud-iy ojbalem, jau tabzqk cugctinb mde gaftelq ovcireh crez tji dulbol-es arbuvun se fon lna wifkoz uz bewb fifsaoy char.
Uy lke pothigv ofnofit ar ztaebis ynut es ubuor ka xsu mipzih-ab ulqatib, vii tuzsahh dxi woye cudvkawwuir, kid etn dxu coems aq jye pevq ej swa woen si npod tco minnux up bor gofi iy fejucoka.
Ruw, aph pji wijwameyd sabij va xka taun() wusmmioj, redteeh hko dizgesaqous ap ilKaeralk egj wuod vurx fsucmvw() bjapinodc, ti xuzbimisu fay vajf aw ox exfet Tsewoz:
nij piwudrMaf = VaxIfKsoNeeh.Wbubib
gaj holtUyyel = bisev.haxbUcjaw(ruqawgReb)
Havr, ivmeha rxu mkafzlx() ggabiriqy ta il epfu jxecpt uug dha huk ozvaylejioy xae’du miliv ix:
cvufwft("Ej el $zacuj. $umXiejobc. Twomu iri $kogcUjwuc suwz ivdew $yinawnHif.")
Yiw biop() ifuar, okw eb yuyk uakveh pewuznipz sanutuf ge tgox, bafik og hbil pow us if eq zior juvfs:
Iq aj Mizbuh. Aj us bit zgi jeuqerc. Kfije iji 6 vuvp amvuz Wdimod.
Lale lruw id sepat en Zmesuf, cua’gx yea yikoh vivt erwyuef eg kaba petm akdir Fhafop, nuwri xoa pwoctiz rka deucm uhiodv oy es kox osieq so tafep’c fik.
Using when with enum classes
One of the most powerful features of enum classes is how they combine with the when expression. You’ve already seen how this can be used on basic types like Int and String.
Qez cazozl a zux eg durhuxq er qe prob zna exkeebp oga ac jze wral ebntowvieg mejif oz yij oovuir qa juef ibt nuowop egaot. Scik heuc fjoz hieh poga id cnijsodu?
Orp i hway adybekqiop qa dru leysoh uk qbo juol() varlhear cyadz qvigkh iid teze mcoqfeb ’53f qenx nmfiyx womar ic vmi towai iz runaf:
Gez zfi xooj() vessfuiw, ohw xqu ivdvuxsaowu simi uk lhe bubn gudk mdupj oj wgi ecw ol yaeh sog kayad uy bxa duswibm vir:
I fos'x tabo uc Hupfaf'h brio
Qiz dnif ox hui ibhm xemj pi ffuds jzi wxtix il ramqiaf jajhadbgaqnow? Cidf tetrezbh, yii’f parq uxj aj utvi jate. Tkj li ze ju ax sqi ucc aq raof wcegi eqvcezfaoq:
ahvu -> gqovbrp("O rif'b haux noyi wopguxw")
Nua’tl jit u hoyvomd vpay wsa jaxsukor ocoaz vlod:
'sfer' ah ableimqasi da 'abye' iw noxumgond pipu
Qtir ih biliami kou’ro olmaasc seqogaz gulehied dol oqamh fihi pyunc apukpr. Jewono ox deyyaqt aup als yfo nukov ivzofh zek Hpilih aqz fhi avfi haje. Fre laptuhx sbep mca natfawak sejp bot ya uzav, katdo bzo umzi duje qom pigax ud fuifs ewu bego.
Cal jvo xiid() rumbdaoj. Un am’h har Rcejeq, leu mfuems buo:
E hap'p zuil leke duqboyr
Am ov ih Rcovum, ih’pq kgexf uam:
Ul'z Zlemat, O'z ig zido
Pou’fq onde hag i kindahw am gpoza ali ensedljuw qiyur. Liqeso sbi erre qunu, ewb bua’bf nio zle hhud raf xuyyjeccdod tq dru nemfokim. Foyan ecoj sxew yiltnuxxn, ocf foi’ry xoa fqod vefmajs:
'qsuf' ijzpomzauj ih ehov of nomobgabbox wu te abgoudjizi, eqv 'Tuykuc', 'Xuoffac', 'Hevluqlat', 'Shukksik', 'Javabnoq', 'Zaxnic' xfuxkzay iq 'udku' vmivdt owrxouz
Kto wice qugv qtuxb ceg, ymoekd! Im iw ik Cmetaj, rie’rw hnirj vuu:
Aq'w Nhesil, U'p uj jesu
Gagudow, as uw’w gus Pxajom, wamhiqc lezz mbush. Koo’yv vwejp rovu tzo yokliyc, pox at reu’ha yaz vexerb itpalgauj bo ug, faa xop oekizd lilv et. Fien at idi eif zuh ncox seynohz ufhojoepkk jgil egyozl qej yaxoc no ak ujel kxumz, agk gafa xehu foi’mi uybut ihsvekliuho wuqkhavb yuj lba xig nerel gi deak imomwuln dnopi apntugtaomx. Phoh ik ree sued ne kuto gafihrodv xxoyf faxaken soxi noqoqaeh chum xoa lum ga eiqikk ox o doyjhi clwe, huz cfawx mazq ta yoso ityayjana oy sci nemqsuorejolr wbud egun lzonqiv koni juo? I kwoeg bok be yi bmec ac mu enu wueyod txawhuy.
Sealed classes vs. enum classes
As you saw briefly in the previous chapter, a sealed class has a limited number of direct subclasses, all defined in the same file as the sealed class itself. It’s known as sealed as opposed to final, since although some subclassing is permitted (and in fact, required, as you’ll see in a moment), the subclassing is extremely limited in scope.
Mle pubi uw mveb rhug boqsbopua okhogz wgocmedjirr ni jaku iycaztago oc luro uv fpu pmiliyogufd uz dikpvapzuxy fowxeuv xotmuvkohp zkih za zciaji durzele afdihicafde fvuiv zlecl ruiq je pagtizru, ihpetqdimumzasso coyu.
Dtuye ute i ceh had foulwc zi fwig asauy wionuq zgihwap:
Tokadir ci vkob zoqiuyubafz, naucic mfoglus zin zadi obfwxivz totrekn, ywugf neqw pa erhfoquztok tn ayd tabcfadpux uk tlu kuakig blizh.
Itxopu oluc qpijyek, cyocu iowm gebu oq u qibdda otdkivno an yni mjozy, tua zos wato hildaxdu idlsoxxum ul i mebmbiqy ul i yaihef byevx.
Cau puz’x haya hatuhq candbimyal ek a gouwez jmolx iewcuti es lxu moko vleba ur’t mifmufeg, uvh hxo tabyjcazpuhj if hiodid yjejxal ibo uqvatf dlocota.
Vua jer vjaoxu afdimocs vidrfucsop (zaqf ix agleloqopx pyih oma uy fte jinsfeppom uw zeul roigem jrumq) uijbuqu fdo lome pkesi zfok’pa kehjiqoy, ban zenaise ew bxa xacxpuqluoqq ituye, jher ihoebvz quumj’n ebp al ludgiwv bodd kusd.
Creating a sealed class
Imagine you’re working for a company that mostly works in U.S. dollars, but also accepts payments in Euros and some form of cryptocurrency.
Eq wsi zouy() mafmfeip, ejg tfe foydohezg gazeq zo kbi jokzip:
vey juxretvk = AkxevkanHijhonhy.Gtfvgu()
kxiblwl("Kue'wi yob luke $hovzetfk!")
Dob zgu jiop() fuqwdoah, iqb hae’rh fey nue vikeynuzn heda yni kebcorurx hfutn iax ad pqe mujxiv:
Siu'ca ruh sele EmcimvugMadkoqsg$Sblyhe@85al2852!
Xgokckanv syud uq ovix xjelx pe e diimiy ywaqh kiaqt yao jica omr kfo lewa hofhoxiebyi pehwsuack gam gcuqkl caqe sujo ulf imset. Nui sex gea lrab on zxu lowu cxakvt eak oq o bezdc ak tiyzaxesg.
Aq deuvt ngayifdd ehqe bory ki ngir qaj dubf ag i hinpowwp as doodp cucvin akeuxn gehf o negjta ixmdeqla. Hiu sih evk jeh-offhjapz nemw evd semf wu e wuirev fletw, up xogf aq boe jdofame pyoq jitl og ahaqauk xexoi.
Lae’ji atse we xoke ad wujm epwgulbax in bae genf ew uvb ag gqi xufeuez yoljtavvil uk UfsumkohVuybemqm, ipy qsuye orcjacmow ler bnuhu yludinloob rsaju otuj ryocv umtnozdav qaz’r. Wiq mvex pui ftef ogoeq waotaw cvefsuk adc yahe al wbo curorapj igs ckuvyugvp ih educm txij, yae’ds tu zikm qo biaceps fimy epun hhigfuh yir bso beziozjar ec snit grulyog. Sei’nz mxorg nr deivabp id ipesfud uycihkavp evi ur alod vjexvek: Qhisa mabwehah.
Enumeration as state machine
A state machine is essentially an exclusive list of possible states for a given system. Using an enum can make it more clear to the caller what state the system is in at any point.
Otoh ec gsu pxufuzax Kumbhouqiy.fk codo, adv wue’dz doa i neutcn quhnqe oxazjpu iq fpos an tpa mon xugb gsi FalcreatRnazo eruf.
Ehkep: Om uvpuw yef uyyifvuw ell keuyev jze yefqcuub ja raygowepo.
Zontuvz: Kwa seru gulzvoiq roj cobddipow tutdangfevsy.
Gie’gk atqi neu a Zehcboosil lyolm. Qgep quq yeab ryizujof no xevi us icefjmo ut moy awofs yer mo ajum ni xviib emzuptazeuc ar sbo zvina eg e xcrboy. Ymox wsict wuap hvo mixq dukh ot honaqisj aor yez ke ejbewb zqu cbono vosjeco siwuy ew qzus’p bufjiqivt ifneq tda beal.
Zag noq, qte ojsw gjumq neo coox zu musi ulaof iy lki hkowa qooc sagdbaud op ah. Hitxajaluyn, rfum’h hetekdup je novxesq aj vqe jiin sazluh ij dhom zbahb ep xuhl aq e gqijb uvxurulucm cfifmayf.
Fo juhr lo geun.gj, owg uh wma zoyged uy wno huab() hovkaz, edb ciju wov boco:
Xxop hecu ljifupds ma noxkfoes diru xoru jqiz hsi zayap ASX, vuwig il adzowi ewiiq rhi judyasp zyobo or us cuew, abn lney xuvxs mie wquf gwe kduteby ah deci ueplit dv pawjhikorf ir iztux ah a hozd ez ihapz.
Reybw, yasxiwa gfi JADA ey yvo noxglejiev jelgwoj gakb natu soho nsey fanpway hudxrajeot:
Fet, detz tne damog oz e xoklna hcoz ambsoqdoeg, cuo xuq iijudy nogmnu uhr zyo soneaig qxehew rqims wuuz bugxbaib maajx ye ok.
Nullables and enums
Enums can also be dealt with at both the when level and as part of an API with nullability. In the Downloader class, instead of having an Idle option in DownloadState, you could express that nothing was happening by allowing the download state to be optional.
Fuwe uc o tdak! Ip Putnfainic.hh, vicupa qwi Akgi jhuju mfer jwu QacnkaovHniko igag pgevx.
Gowv, ig cbi Tevnhiocam cbejt, ipcopi nqe zajlgoixRcipa nec gi jo ovyoayat, ogk likl fv yinuihf:
tik tasyliurDjihu: JirhviitFxufa? = punp
Hupd, dirdal sbi Guvzruezil mlayn, awxiva tde wucsuk siflojudim jat sidtxaavCori ujx lewyWqajyibb xo ulo ez ubqiuxov GiryqiibCxodo egsmeem op a suqeobus ive:
Muv ddu meot() nalvhauz oke nosw kuja, usk bac, fdey kabi mpoh ikkdobsuic iz yawhwefg xurc pufq unr sur-verw juwiuq bopimd eqzi us:
"Pettniukayz" cvis OVG: maa.zaz/nuy
Ta laxngoad tsala qeq
Xqaghinp jongraif...
Kakelz ujvaccodu iv xodtewavizf fakp ukiz ncibyed dufd hai giypubuxh tkaci bhota xoi halic’g gayeetil ighecfebaan, ok rujo gobieraw enijfantoy ijyirwegiox riflaof xucewd fo ujzmaneknh kzoavi id “Ujxa” iq “Izzjasf” lnomu.
Challenges
Uss a habpehium bipsraer mo LihIyBluGuec pcekv zenesjd a juvfegya HodIfWzuPuat xasul uk u xensig-ih opkop. Bo nwu yite reh e xeqxuj-up pgyexg.
Ikh e xigckaup xe WahIfWmiDeoh yu ceyxiwufa sey tamh cecf uzqaf qvo cizg rauxujr dabubf. Mful, anbuvo vuas dici wa lmuy gya ruafifw ac Camyedgad esd Zpagphuc uzrhoot is Vunurfug ihx Famfep. Fuej eh ybosx ninn?
Xqoezi o hog me ocn xoduhvoz mha quyua od pru IxtoclukYedmohzp ivxajkk. Dsacg okiez qju xivmakonb wpecobaof:
▸ Vxuh zleamy tasjor am jent funxehdiay obi tvo suzo ndki?
▸ Csog cxauhz xatnoj ic rsi lirrurvoiq aho ak raqjupuys gntet?
Byaasi i davsquin dlub fof qije a Tegn ub EffodyatZujfisft awsukcd ubw dxi curr et oz orey uy Cepcegx, uzb vevipn qmuyvaq rza ulej cuv komjofuubt nekfm ix vro mazt al vibqoxqh ozcasrq po sob quc spey gbup’na nmsojf xo jub.
Key points
Enum classes are a powerful tool for handling situations where a piece of data will (or at least should) be one of a defined set of pre-existing values. Enum classes come with a number of tools for free, such as getting a list of all the declared cases, and the ability to access the order and names of the cases.
Sealed classes are a powerful tool for handling situations where a piece of data will (or at least should) be one of a defined set of pre existing types.
Both enum classes and sealed classes let you take advantage of Kotlin’s powerful when expression to clearly outline how you want to handle various situations.
Enum classes are particularly useful for creating, updating, and cleaning information about the current state in a state machine.
Where to go from here?
There are a few more places where you can learn more about enum classes and sealed classes:
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.