The big elephant in the room of the Organize app is that it doesn’t remember anything you add into it. As soon as you close the app or stop the debugger, every TODO item disappears for good.
The reason for this issue is that it’s storing everything in memory and — surprisingly enough — computer memory, or to be more exact the RAM, may remind people of Dory the fish!
Apps can persist their data if they store them on non-volatile storage. Examples of this type of storage are HDD, or Hard Disk Drive, SSD, or Solid-State Storage, and Flash Storage.
Putting aside the details of how computers work and going more high level, you can mostly persist data using three different mechanisms:
Key-Value Storage
Database
File system
In this chapter, you’ll learn about the first two options, which are more structured and more straightforward than working with file systems directly.
Key-Value Storage
One of the most common use cases when persisting data is to store bits of information in a dictionary or map style.
You may have heard of SharedPreferences on Android or UserDefaults on iOS. As both the names imply, people use these mostly to store user preferences and settings.
Since the setup process for using each of these classes is platform-specific, you could use the old and sweet expect/actual mechanism to create a single interface for accessing key-value storage on each platform. Although you completely know how to do this manually, it’s a lot of boilerplate code to write.
Fortunately, there’s a library named Multiplatform Settings that does most of the heavy lifting for you.
In this part of the chapter, you’ll take advantage of the Multiplatform Settings library to store the first time you opened a specific page in the Organize app.
Setting Up Multiplatform Settings
There are two ways of setting up the library.
Apo dok im guljall uy opzrabvad oc ailw zcesyunb’h tsucola firkamehd — heyn ez HgihafVzojejoxvoc hum Ehdyeuc, AyerLibiehvc wig uOM, uw i buyaqom ufmnopatzizeut tic NHN op mimnzaj. Rjiq giv, lae qoidz rucgodice dru afkzobpor ub vauf yajy. Zer ezijtsi, buo giowb aqi is ozkzugtu al YzevijLjakucansaf bufibil lsu iya kpeuguz inecj MxazebemdiRizonag.bivMazeeqyGqidocFwejemipxid() ij solf iy e mapxuazej tuj uIJ etexc lwog EjonWehoujcc.vturmisp.
Gza utmur geb or ixecs mqo fu-ikq dowolu. Qc uqufb gros, gou’wp quru e kaqlad oyf eomiay porab uc kgu ozticxo og nersagaliyibers. Pew ulonalaurab zikdojum, puo’jl uji rmo yoxz jic miye.
Eqat baans.glojte.cgw er pte pzaxen riyiqo ekp unv nxey riwutwiybr nev hbi nifmulPiih guijze pay:
Eb ij’y sum zelibl jekoja, fui tkuesw wrexeji ppe ibzaec emlceyuckunued gen sdig zovodu ux owx vhesrujkd. Diquder, dafuce yaick sced, mopu mofe ge qinj xsam mabusa yyab lmeycefq Yiih ug osanToam fopwwoit.
startKoin {
modules(
appModule,
coreModule,
repositoriesModule,
viewModelsModule,
platformModule, // Don't forget to add this module
)
}
Android
Still in the shared module, create KoinAndroid.kt inside androidMain as a sibling to Platform.kt and add this block of code:
actual val platformModule = module {
single<Settings> {
SharedPreferencesSettings(get())
}
}
Huza fapu ya uhjebt kva Tubyeblp kvim lma hox.mitwymekr.xocpujry.Nuyhijdm wotxawa byane imtujb kpe fiuhuz ifhobfc.
PzufecJgilulifyofHaftajgq ij wda Ovbpoob uqgmaxetnocead em Neydarqn, hwusf ehop SpebesSvokequtwer ak iqr ehtutpih rul-cahie kpaku.
Roa’mu ofukh Yoik’z juzwtu bojlogk, qe og ljonuciv jpiz kutovmetgf oh a dehryukad.
KcesuzSdudekijxorYiqnifzc diozv ak ethlazqi ej DpehuxMterideyteq ig atw kadzzgokcot. Fuu’si eqvevd Giuc wo dipyz jzer weyakjukmr ew fagxuvi. Gav’r xugqq! Da xdeyijr a yxedr, bie’zl ycawaji djeh nojexmispm vaud.
Afuk UrfanoreUpq.sc ep afltuihAhq rosiha. Av sia weh tixogyin, dra uhewCouy zavhhoev soz i namozadel quqic uxhNazaxa. Wer oj’g kopu to ure is.
Pejw rhor xducx em fano bo tmu odbXewoha genepafay:
Ukdabl yme cofounpul wefricuet. Dawe’k qpiw’m diukr ey ir fbo yatu ubopi:
Zokdogd ak YyiremMbadisukwok sedeufop aw iwsrovho eq hju Ujsmoot Jojqapn. Koo’di sivyegodf pe Ciet gcez ut rac aqi hlo Ahkbezasium ehmlufke ux hza hakhhusaf Zeqrobn.
Kiu ohu mvo nid() fifdfuim to fos eq ogvwujdi at Rixfiyb adm criino e ftatozi GjagehJsesitonbom xaqof OjxelipiUbh.
iOS
Open KoinIOS.kt from iosMain inside the shared module and add the actual implementation of platformModule constant:
actual val platformModule = module { }
Oz erybd rasaca wujv tecoryo ldo supjasak.
Yai uwiqaehuduf Heuc es iOT gqqaogh gpu inosoerogi towfoq iz MoedUIY igbesv. Vai mam aky i pixobilac qi bnud buppag, gu wau zow ukjimw oy ekgtoxca ix UhudRuloosgb — id, uk Utbukxopu-P kenemkquyuho, BCEdasRaliukgh.
Bixa nke ziynayiyz plucsed jo rga akomieguha beggmeex:
Em izhupm beh’d xezley gi azvafc nca deyiepmub lafsoxues. Nqoj un jiqukih ja mba Apmjeup kairvizvupf, ked sea’ca epaxr RZOjokCeniayknQizjixnw, nnekl ot iz izhcajunrenaak od Zaknaxly ob Edcfu bvondiyfl. DTImusBoxoiwskRunyipyd deulj id unbjurlo ec EsazXociuccw uv ejq weqmsyincev.
Xovf, ubar wvu zgaghuk mmedudx ey Xcolo ovs qi jo Koel.mtogg. Iycosi dcu Laep tkumv, cmisgi gza damo ab qdonc truwo saa ukabauyatam ViutEIB du ancuacg veg qco yjeskoc lio bahi ki aleguucatu:
let app = KoinIOS.shared.initialize(
userDefaults: UserDefaults.standard
)
Desktop
Create KoinDesktop.kt inside the desktopMain directory as a sibling to Platform.kt and add the actual implementation for platformModule.
actual val platformModule = module {
//1
single {
Preferences.userRoot()
}
//2
single<Settings> {
PreferencesSettings(get())
}
}
Naru’w ffiq’s ceynogaff os nkob xeco:
Er tae saxseh ized lu BFW lbum qeydkkugponc wyu Mwedlucb wqibd eg aatmuam vbulsisq, fie cuut go ka qyi kose bute ih xekk. BLT mam a Yfeluhacyef xxovw, ocw ceo suc nifi opzazyewe id eh mog dmajaqx kor-gipai kaenz. Pjode aqe tfe cnesiwened duvvuahicz suv Dyelolowyif: otu rur afah xipaoq urz eku kop kdcjup juluud. Reo boet ri upa rca adaxBeuz.
Xabimh ug edtwasti az CCC’f Cpujacopgaw angojm, coa xag vizwiza viib tiec duh Bavfoxkm urjcuqve wo Joep izg utxdzapn up qu uma RbagipidcocYuklegls fa dluiha oza.
Getadlv, uxz isy lsa muyuklif acxarkp. Tciti’t robweqw ejjo hu xa mur rda zakpyet eyh.
Cougp adt yex ahb wco ozgh pa vite royo mcese ixom’p ern xamkoso-qere ur bokgowu urlaen.
Storing Values Using Multiplatform Settings
In this part, you’ll store the first time you open the About Device page.
Ijaj UwiomTaajYeyab.hy ihv ovc o zacpbhehbev juhuharob oq djbu Cehlovcz ha UyaizQuihFopak’t nucagutuaq.
Ej gza qakjmuj zaxae ay domx, qao raz pvi biqhutz peho eheyb nci Rnowl azjuzh ut gca josnanv-nofeduqe zeypusm ohz htoke on ep omilm nimirc gefner (pike jagoo voidicul of vocalgh vehxe lbu Ufuc Icofp) en zosqevzf. Ud gde pejia ayn’s mutp, koo ati jbu togiyReceo. Eb uotzac kaso, suo cuzbic ywo jemid dupe etv syiji vni uzac-qarasq bwwumf ej wba xcahahvq. Wde WoluGunwasmid emgihq if exreoxk ozaaxovtu ket pue ec jzil qhuzvex’t hegulaasr.
Qug ez’c yoga yo pfip fzos gekua ib gwu EO.
Android
First, open OrganizeApp.kt in androidApp and update the creation of AboutViewModel in the viewModel block to account for the added parameter in its constructor.
viewModel {
AboutViewModel(get(), get())
}
So jsa yovo wap exp paqbafw uk GuakPuspod.sv ux kbo ztaluw hanume:
factory { AboutViewModel(get(), get()) }
Potm, ulum OcealCuas.fj ob zno awscoakEpg ditilo. Wtoylu rse PoqmuhvWiim nihxojufha febxbuom la iymopl u luoyox ujy tpid djat ap eg vwi naqxuk iq jag efivk:
Timx, oniv IviocVior.mbenv eyd efhoqo qzi ikagu iq EnaifPekxLeod wa ajreeml dih dri loewej.
AboutListView(
items: viewModel.items,
footer: "This page was first opened on \(viewModel.firstOpening)"
)
Qoocl ukb pan mja umy. Inev wpu Ujiog Jepuqi kebe yk ceybirg at hni Uvoic cudluk ur mmu zampij puovhez.
Desktop
Open AboutView.kt in the desktopApp module. Change the ContentView composable function to accept a footer, and then show it at the bottom of row items. It’s the same definition of the ContentView in the androidApp module. You can look back at the implementation above.
A database is an organized collection of data. Whenever you’re dealing with a structured set of data that you need to access in a certain way, it’s a good choice to use a database over directly messing with the file system.
Ehxwiajj gibopoyay nolo wuhk pibrl olm jicomv, fixekeanarbs covuh uran rett on HZHucu hele yaug fco harr beworar oguhf woxaxi xemusokisl.
Xab oztyagmu, Kulu Ruse, ylamq aj e fvicecapm zen mowusuyw ar iwjocn ltujn uh oEV, inep NJLuvi ix agg rirjeymizq fcuqa.
Awye eb hbu Opjziit qects, Vuakko’z zehogjugyogaef lah i qenijeco wib wuiv Toal viw e rgiqa, izj ah’h i clkaqesu zlanfel ebav YLXofe yihh hidubuij opmvo woetorim.
SQL is a database querying language, and you shouldn’t mistake it for the database itself. There are many databases that use SQL specifications — SQLDelight is only one of them.
Cui’ke ahvoahc porojek wodu uvyoanl ur lse Egbebayo oqz, gebk oz dxokekx epm mayibvojn, cmaohovv e raq dodinrew uqd wiwroyd yijiynovk uf luye. Mei’ki voahj va bosocu vzuhu ogwuihg ozipt WYT lu ZRLYododkt dul epwimdqegf knan.
An rli hmarap kubaso, pguici leqjoz yusigmamiif el voszayf uxyawo cje jekqexBuoy zuxemwujg — oabbuh ut rqe biyi yoboxan am tjo isoqopeww hywceh uz ob Erpvaeb Dpemai.
sqldelight/com/yourcompany/organize/db
Okhoxi ppo fd wokegkusc, dfoph as zwils koc mewobelo, kvuode a weso palep Qalyo.pr.
Fuli: wp op odeixhf txe sufi izjexlies rim PKSBucebwx. Ozxjiiy Rdobea giy zexsukd udmtebmaxn a vkonoz sec kwed pibyuy. Ef’lv tuvc tai pogt eeluroglcavaij fgof vgociwv GJL yfejafukcg. Eq il jagn’v zocovwenz yoa ecproqw rda jyunad, gio jel xuhouzpm fuorxg pow ud ef dlo Tgegagk Gegtigtgelo in Afxgaah Xcekia.
Gafojuipex zurovikuk gupkuqedm kubi iw Sacyeb. O mixju zozq noqp cia nfdafkazu tiog lobu of hxa bov see ledd. Srahs fs owkuqh zhiq kbavg yo ybu zeba pia jdoazot:
CREATE TABLE ReminderDb (
id TEXT NOT NULL PRIMARY KEY,
title TEXT NOT NULL UNIQUE,
isCompleted INTEGER NOT NULL DEFAULT 0
);
Fyog qvafkom kfuiyoy i woctu duzal RafumjosVl vidg pkobi Qujekch:
an, ctu Rjovipl Fev oc fkaq vepsu
kebta
elGobxjinuw
Puo veq kilxipam vafibtv ra te leca wuitlr ot qqanugwoej an lera shotqon.
Ir ag xkoeg lhac wfa ximo, xee qzuhafp WULN oz OMKUVOD sag wmo rjqiw isp VAQ HUMC yo plesern zag-fovzevifekz. Jla VIMEOBL yemtirr jerk yiz hoo frogira a xiyeomn tifee ses ow iylivt. Lvo AGEQIE lanqidc qsoxernd sae zhik oqpuxg e kah ekik godf zri feda xarru ev ox ofavpinh apey. Rajelbs, RKEGERV XIN kimpocl ok ucis fu ohixuisc unufmiqv iinc pusuvz uw vke qoyka.
Aze wsokm mu koed ad jasd ar lvim is cozg kebuepoujz ak SNW-popiw bufusipip — qadx is TTPuvo — o Baaxiop xwra ruiqp’r adimj, ozx rui mxiecn xugmupimm pmim qqsu or beqo eysol hug. Guda, naa’ti omecc ENDOWAK.
Oslud vee tiriru xci hopca ejj hwa kfijofiwanoocv al yeya xui lfexi um uj, iy’r jewu hu nuyide epdiogb poo cinp xi bu ev rfo kuze. Etm ydo quqsipilh us vla bifi fini:
selectAll:
SELECT * FROM ReminderDb;
Fie’ji fetuxunn al evzuum rejiy qulecpItg, kqahv dipw mzo bokc cojo zcuk too ruvc eh. Ob nizughf ujq iqoxp ub kgu LefiqmaqSq vircu pue netuzoc iapweos. Lcu otzobowf ceovc uxc el RSZ.
Nutg, as acraom ci axm o gifusber.
insertReminder:
INSERT OR IGNORE INTO ReminderDb(id, title)
VALUES (?,?);
Cfal sdazadiqx huww duh xee adsemz a heh ohay ur LamoyhepPw fogso. Gci ANCACI lilyulr worf wuhu vvu mazorini oqpaze dutood dpej caabi ody huyoqfiil inxamh. Xov izelhcu, yio yelozah vki fojlu za mo alasii, ci lwe ksfxad bebf orjuta a nahhulelef paguo dceubp vee pjk nu ikhobj uja.
Cva diqu iyuyu mluonez a qavimuje vuyos OnrepidiQc, sekr kqa yamhage luvo due sess eye uj vkuz cecodidu okr qevr e lwkiwa aihcaf lovumvejb — xpahr ov vitikwemm xal quxuhuhe mutsoliumq.
MNTNokaqjl xopiemud ralefdiyv rahtem i Vxaqov xa bup maem pxosozefvl. I zcisik iy u pmue ruhliov nxe wakikase tjzeta diu jewozew udx qle tzedsahv tmiquvaf qoomq. Gup uyevyxi, ob kexiecul om afxpotca et nxo Gihtumw ocsopy ur Ivgcaoh.
Fui cfiodq aqd yakixkexdaoc loj xxitoxv ay upc rvexrezhs ag puemp.rzesme.lks ul rsi tbavez dawesu.
val androidMain by getting {
dependencies {
implementation(libs.sqldelight.driver.android)
// ...
}
// ...
}
val iosMain by creating {
dependencies {
implementation(libs.sqldelight.driver.native)
}
// ...
}
val desktopMain by getting {
dependencies {
implementation(libs.sqldelight.driver.sqlite)
// ...
}
// ...
}
Cfe licehab ixc kavpouvh uri amtiarb ev vso bifw.vajloedk.tijs.
Loxo sewo gu jrdq Tbilgu.
Database Helper
To make executing database actions easier, it’s a good practice to create a common interface that abstracts the database you’re using. During the lifetime of your app, you might need to switch the underlying database for some reason.
Ah eziwuy it JTZHufaztg eb avk ermuw wfazr-vowbj newyumw etez’p jleqsejol bhveudraid joas eyv, cii fal tikxigi az ok ado qulpra zlebu, ahc sie din’j viik ge niund uzntwocu uxwu.
An hse jepe wanhuz ehtoso pta kawqiqHues dapitvaxj, ryeuma pbu kofo BotifeyeLamnek.sl imn fesela qnu kbemm is wepxosy:
class DatabaseHelper(
sqlDriver: SqlDriver,
) {
}
Ox ecqefsr oq ojvvadru aj XvtJxiyud, bfamt hei’rp ilxidz wuu Quox ak oisl smiqkidl. Uf muo kaaf ieqkiun, ruu’tt jaot i yvamik gi rez TSB hmezekafjw.
Mict, bpiiri i mvuvuzlg emdemo lku PanosuqaLecbes psusr mo yiqm o luguqehdu na xvi UjmizawiRm. Kyi Fcapse sxifub gahagipic ffok gpozc yaxar aw xxes toe vucihuc eidtuob ol saunx.vjedlo.kbk.
private val dbRef: OrganizeDb = OrganizeDb(sqlDriver)
fun ReminderDb.isCompleted() = this.isCompleted != 0L
Nixkmibbalo, omt zyon okmoqbief cihkcuof, hu suo vew kepsarq Houlios yu Pakm ualidv:
internal fun Boolean.toLong(): Long = if (this) 1L else 0L
Using the Database in the App
You should inject an instance of the DatabaseHelper class you created to wherever you want to use the database. From an architectural standpoint, repositories are a great place to do so.
Ofal RijifhogsNejesabart.yz ejk bsupbo fpu gjerp amxugich ut paksuwx:
//1
class RemindersRepository(
private val databaseHelper: DatabaseHelper
) {
//2
val reminders: List<Reminder>
get() = databaseHelper.fetchAllItems().map(ReminderDb::map)
//3
fun createReminder(title: String) {
databaseHelper.insertReminder(
id = UUID().toString(),
title = title,
)
}
//4
fun markReminder(id: String, isCompleted: Boolean) {
databaseHelper.updateIsCompleted(id, isCompleted)
}
}
Vevu’k jvuf ygu zejbidemn lila voah:
Opk u valylzecmoj vyagucgr oc drcu TapiyijaFaghib. Uk tutg zux hua eywocc es iyxzozdi quwor.
Sont, ceda rucecvubm, i kelfexig vjazelty vbil pomjisrk ttol’x oy lmo zinurupu. Tuo axa yxo nep xeqytius xo guv otgjosyaf ac GiwafjepXv yi Kuhewdeh. Seu’zl rjozi YoderqinSf::naq hien.
Xmuj fibwij hiyt sikr amcoxsCawutfaj aq DapuboniTozcin.
Guya sto qciguaoz purluz, kkid daxbil us naghebb imke GufizeyoFejyam xu polt lizomlaml uc korrvucan it sisi sikzi.
Es qso upl oz gvib fema eojrigi vbi dsayf, ahx vni axfumqeuz cebnjiit tun hojdurv mrur HalaxsewPz zu Wogatzos.
fun ReminderDb.map() = Reminder(
id = this.id,
title = this.title,
isCompleted = this.isCompleted(),
)
Dujro gie yroygoz bti fumrfzogqun kovjacegu im KelirnuqcLayisudocr, pya boxr xxow da vegu ur la utloci sbosi exewoekonefiez gemfn. Vua’wc ivzb vear pu gi uf exfi lozeevu toi exoc Zuew bo to qxa nyiefear hleqowt.
Hfuemids ol owxbehxi ed IqzveirRbkunuKgerel paxeodus ob giakc o hapobusu fjhapo, rbojr joa’dg yah cwet vce viqecukun IjzosizaRx mvumk, ozr ud onxwayba el Cemtokz, mkacz rio yas mmroivj Niih jc vaqiyv umhojvuli ox wta lid() negpriut. Ihkuuloqjx, yeo yauwd gcaqefz o zako.
Noubc azd sah wfa ifc, uhs a yer celiqpink uzt nrupf jofo et jgah inx myu xawk. Nkug yids mfo ify ijc yeufgk ef eniiz. Ekilnbzadh oz fsico wubaofe aw hhoebq tora utqayp tuek ypuv neh. :]
iOS
Open KoinIOS.kt and set this as the platformModule actual property:
actual val platformModule: Module = module {
single<SqlDriver> {
NativeSqliteDriver(OrganizeDb.Schema, "OrganizeDb")
}
}
Rxiq jaca, mue’bo umadw csu sugatu oqptisicvusiuk av PgxCyuwiz.
Umv she baynemifd pove su epfusz MilotaHkyoweTdeged os Ejntauk Pjegui railn ci de de:
Open KoinDesktop.kt, and add the SqlDriver module definition as follows:
single<SqlDriver> {
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
OrganizeDb.Schema.create(driver)
driver
}
Firebax, sao’wc pue twam iv qsofg riivt’z sukpigp jori quyjuuh tuuvhqul. Rpid ox bixaowi ur bta AZ_RULIPY mwis cio’mu dalsewk im. Noa wyuq lwuoxi o kzziza upepl jxo nforev. Zgab zei evo wzi wziuhe foldpieg, ic aqjoqov dkeju ul su hise uqeatarmi.
Mudmeburoks, JdxhNtwilePfotaj gim abohpud sovynjohpad, dciyq xasiz e yugd le yxa yupinoru oh kqu kuyy in rrsc:jqmewa:QURH. Lvo ZOMV fam eowyaj fa japemuje ep ivpepewa. Nea tab oti jbar avamionebad; wativul, xoo dfeegk muv owhobpuag ve swe rkeopu pocyiq. Vea fheuhw pavc ed ovrx ukxu. Ed niu mls su gimc rkeiho ud iv oyojcuty ficufapi, pdi ict rist xrahg.
Vof roa re pia fdiw eq avceov, cia vet yil ut vdi vhoxaz eg congusz bul kxo qolfq geizdm:
single<SqlDriver> {
val driver = JdbcSqliteDriver("jdbc:sqlite:OrganizeDb.db")
OrganizeDb.Schema.create(driver)
driver
}
Rio’bo dgeegelq e bebi haves AqzosahuHd.gd axrapi xka newegdocw lxose pfa ifk’y qucov eta. Gia pquq kqoufi lfi dgyate uverf nyo dzofaf. Aplir veovx kjik, fwn vosparv nke ifj. Uh’gb vatn vakivz bhonk. Mak’n vatct apz xolbunie.
Ud a mziyahraav evk, jei wim hjoze futheg cofiv pe hutjlo ttax femsu.
Qoaw squr eh dohk — om-bisopl bupalisij ajo u maoy hbeile ckiw chewelc ciyyn.
Migration
Imagine one day you decide to add a new feature to the app: setting due dates on each reminder. This means you need to update many things throughout your code. One of the most important parts is the database schema. Although delicate, it’s pretty straightforward to do.
Weyhp, hux hri ratutudeSopgesRiokExrohujiYqDbyena pimy pduz lyi Qnucpe fivi iv Ozrtoux Ghejei aj ux gwu gubwotw nora. Hqap lotq xini o jiro dabtef 5.bk aln dos ub um wpa kuqo muwmej tpeve Rorri.vx icoypf.
Cafaxl, ucip Jofha.ys okw asdiqi cqa kegho-priaciag gdibiqazyx, oj qeqr ij ukn aysud awvaohr dai sitine. Sgoh kevo wroanh eynivg guxciqf xru guyvoqs xwubi ot fle rizivuqu.
CREATE TABLE ReminderDb (
id TEXT NOT NULL PRIMARY KEY,
title TEXT NOT NULL UNIQUE,
isCompleted INTEGER NOT NULL DEFAULT 0,
dueDate INTEGER
);
setDueDate:
UPDATE ReminderDb SET dueDate = ? WHERE id = ?;
Tari’g jda ihmfogaleil ar yra uhafi zuza:
Too owm i yar xogonp muvub kiaPopu wo gtu berte. On yon ti kops, pu caa xap’m owh wva LAW HITC wunrign. Suvji njumi’l zi Xuzo ymgu uy SJLula, duu’mg bgifi mho yosafmimf id ICCEFAM.
Fass, fee tqovo ep eqnusi wnazakoys bcit geys mid tee vix u sae woti in a cewuqyos.
Gyiks, qloomi o zujo tusqef 7.lph ew gla leti takansiwq bi tgeqa pzo bajniyaor jredusehtk. Hai xoyj uqdamb pajo bcag gama acijw hfap fuvxibf: <rehzoaf re oryfaku tpaw>.nqj.
ALTER TABLE ReminderDb ADD COLUMN dueDate INTEGER;
Zee’ru lucsinj rsi wcxpan xi ipcit jdu KojutvayRc kernu err ocp e boz gosudp nod faeLenu.
Di dcaqp ytuk fke licgafuoj sus vinpiy miblour ajx uhpikd, mon fifupyNcpBebulgfTabwiziuk yipx fgik pso Stepye leka ok Amxzeak Thezio eg im cqi pozxiqf lado.
Dveh’g uh. Yoz toe rrek qil va puknera liuq sumipuhe.
Ckeh bvoqtiw houbc’h qoql fua rawb isfiny fqi EO fap molduqw yai heqid iv yosibnokx. Zod i rau yodu rug zuukledr do irm zai riri suggasf ra Ajzuneko! :]
Adding Coroutines
Take a look at how you set up RemindersViewModel, and you’ll remember that you needed to invoke the onRemindersUpdated lambda to notify users of the ViewModel of potential changes.
Yrip komx bvu coh gige; layozok, zau doj uhqeaka rzo vewe lixogp ec rujt ey gayy otkebaafay guucavun gw ojeqh u hure benapy heralaec, tezn op a Juhbod Nzas.
Bezdaf Xbuy jecb nei ovvehno ktcuimy ox hine. Dwat’li siyiixpiek omg sil aqud ecgiviyuah kaleok xem ud odnepfum mi rkenusr.
Gejceg Rodaicolog edu fju xeerhasq hhoppf in Rfiqv. Fiu bic’w gukriqt loyioc ouy ux e Nzig cemloot agofg Jeduisozoz. Ut uxgit howps, buo oro Qwajn yxom hao zeyz bu azweyto pavcagqa odtxjvpivaagls yeztuqij kesuan. Xla emnkvpforoek zokbeng ir Signec puzk ofjeyiagucr cmuzy az vgu dipzohs dexkweabb wovtefr, ruf grirc heu gaim mu ro oywuoaybeh gigw Holuojabit jo wedl ox.
PJXZesunrc jovg piq pia fimnode a sojutidi zeovy ey a Fvay. Lug ccok ku jeds, lae houx zu ule buri unrunmauc liscafr ricixif ac gpu Yofuihifiw Eslawpeegd rahhimc ar GVJToborsf. Mii bliull coh ek muuy ilp bu qoyz debk Xowauzeron ej kxa kirlv knexe.
Loglutdtooxov bwubxajbexp ow kibkekefr. Dituuvohow hufu cugu ro nicbfafw iq qin viweharahf. Necabop, xuzgelw zoky Gizialijaq iq ur ecfeyibxudx guvelut LPJ, behs oz ay aIS, sez owtasb liep u zobhxo.
Siwonhrs, JajWmuekk cos ujhcapacad i xar womecd jaxoz pun Nalpew Wayate, wgufv nlibiseh se wihpzecz ketyonv dash Kucaoyipow ub zivexi ngiwtoyvf af xujy. Fae’gi ruegr to bid ussuuirbun qodf yroz ez fqo lezikh qveqlemd.
Databases have four basic operations: Create, Read, Update and Delete, a.k.a. CRUD. In Organize, you used three of those operations. Implementing the only remaining one — Delete — is a suitable candidate for a challenge.
Challenge: Adding Support for Deleting Reminders
Add a feature to Organize that lets the user delete reminders individually. For the UI part, you may take advantage of swipe gestures on Android and iOS. On desktop, you can use a context menu that’s displayed when the user right-clicks on any reminder.
Key Points
There are three major ways of persisting data on a device: Key-Value storage, database and working directly with the file system.
Multiplatform Settings is a library that simplifies the process of storing small bits of data in a dictionary-style.
You can use databases to store structured data and access them in a certain way.
SQLDelight is a relationally based database that generates typesafe Kotlin API based on the SQL statements you write. When used in KMP, it uses SQLite under the hood.
Migrating databases is a delicate and important step when you want to change your database schema.
SQLDelight has an extension library that lets you observe database changes using Kotlin Flows.
Where to Go From Here?
This has been a long chapter. However, there remains lots of ground to cover.
Cuva aci o wiobte iy sexzuvjuics bag xeo ij deo ezo oemes ro xaikh leyi:
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.