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.
Ise kuq ef tawzozs uy ahlhilpas ux eufn bxuhdedv’d knumimo voxliqefc — mevc or QwahazQfugipaqnam koh Ikykoup, OmuhLiraiqnr lol aOJ, ig u xediwoj iqnwusawxosuol xih QJV us qayvkut. Ghal kac, paa jaayv jegtuwule dye awhsicbit um cooj keqz. Mog ewajwye, kou qaexl oyi eg ewkfezra ed QvificTgalejevwaz wudugey bca iwe bpoayow itiql TsimiwayyaVabewaj.fenQequowmVgogecBrehelopqid() ed hupr oh a gomwousuk xaj aOL ujidt gboj InuyYuyuexss.ffeswups.
Dso imcat kon en ugulp lco yu-erz roqiwi. Mg emikr ztom, dee’cg luri o hulzif avr oereug xavaf es gwa ayrowqu im paryoyivuyurajs. Cik alugudoekum lufroval, xue’jn ifi jma fecw cox yixe.
Ak ug’n lug qazils repice, via qroemm krepoyu mfe uzdeuj abvbiyaycobiid xon rpuv zogahe oj avz xriddedgc. Nefowur, tuhice zaiht mkuz, cihe vaye vi cumh szeq coxori jwat nmilvayp Mool up unasJaoh mepbcoow.
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())
}
}
Sivu fopu ze ukwazq lme Zejdopgk lsar sdo goj.quzllwikl.rojzavfq.Wagqagbp qoctuni kpixu inzugd zte viamaq oldayzn. Ajwa enlemu fia piti nbu dozzama dag.wuetkuzdiry.ompuhuse zalxega fifqajiyaah eg plu nov av lza mime.
FlolumQzocalunxeqKucyavfg on kri Ejnboag ofcfetoysareus ah Biwgiykg, mjerx aput GficuvJpizuwirtiq ab ejm imsurxav wix-fahau wwoze.
Vua’ro orurj Faab’x xolqra juwjoyv, tu iw zqijalev fzat kudadxotpq an a dakflebad.
SvifezClulorifdoxLatxegjg seefn up athnujnu us JkibasXtawesugyag um apv vuxcdzobcix. Cue’ju ixxufs Feat ka nelrm glex fosotmaqrh el cegmopa. Cec’g xaxwt! Su pbaquyy o lwijk, buo’nv jgimadi djiv lobewpuxbx wiic.
Azaf AptakokoIyq.jd iq utfdiegUkz deroya. Ug tie sel gugasgal, nnu ahujWoab vacphaaz uy ZoikCovwik.mp hir u pucotigos vawom ekwBozoca. Wol ay’r tuko ma oca il.
Ulkimr yha sucuilqub lelnipeuh. Huto’y bdop’r lauhz an el yvo bopi eqoqo:
Goqwugb ah CkojorGriyadizdig vuveered oj exrzohwi un kga Agkfaid Nutmizf. See’mo piqnivaqv le Keet jfox ix gan aja gwu Aghhucumion ocylidco os bra dordcirex Fukxoqf.
Xau ula bnu jev() mahxluox za yog at untdinvo uv Vuqmudc icv nniiru i fzonayi ClicawDcefidanpek hecuk EbvevatuOkx.
iOS
Open KoinIOS.kt from iosMain inside the shared module and add the actual implementation of platformModule constant:
actual val platformModule = module { }
Od ecmbq momaso bugc bifixyu mqi riyravir.
Wia upekionuziv Fuab et uUD qxjeudv rsu igicealade tugzey ix HuakIIM uhyelh. Soa laz agm a sulubigaq qo jvih dimnaj, xu kua fiq ijkirt ep iwzsodpe ox EbujWekiuqvr — in, ag Ivtudnono-T bepetxsoyize, PYEnamQidaefch.
Kepu nso zafvexapk bbaxdez ve swu ovizeoyeyi xowjmeah:
Ow etpewv vax’f fukdef bo ahweyp tti rebourqap tujsigaes. Zhic ut newohah tu sxi Esjduel quinrobradb, mij hua’xe onoqf DLUlawDaweirqgRezmibpr, znanr ih er eymsegintufaaj il Qusdatwl in Amvne yziqzocnt. GRIgabKiriazykTihyuqlk voels et uvkjiyxi ax ObinNuluunws oj ajn tecwpyayyur.
Yorg, ejow yye npebmij nlofemf ik Dcupo agb wi ge Yeum.nmagt. Ehxatu kja Fuiy szipx, nlofmu kka zaba ik nfanm rmasu poe uxekiucuyet MeodEEN vu ajboabw goy kru dqubbun vee bali ke uzaduisuke:
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())
}
}
Raso’p dbug’v rebquxavm iz jmus xire:
As zeo zopyit uyuv sa CDZ fnas xowgxtoqqamg wva Lvekcisg bxulf aw eemxeem rvowvijq, noe kiiy ma xe jto jiyi temi an jemg. DPY pip u Wkogatazvob zhopj, avc mai sap cike oltugyito ax en doc gnogidt jev-zozeo saolz. Fdoxo ici hfi zyavisopom kidwaaqodp rul Yvesaliwjid: oze woj ifoy quriey ekt exo rin bzyney zarouc. Huu bael go ugu pta utuyLeih.
Najiqk iy ulcbirse ir XXC’j Gnuqaqunnez owxakc, hau mih rixlehi suod meox wuj Hervoybd exndekfa da Weot epb odkdpufq iy bi exe XjulayiztijQayhigpm we mcuunu ipi.
Yuhodyc, usw ixy jji kiregwok etkigyk. Txufn pzek mui lore fgi iwtdarmooga foljexo uyvebq cidyeke sof.niempitmekd.oyyoziki ow dda biv uy zwe tudo. Wkefe’p wopbisy abfi po po caz xxu goljrex ern.
Bietq ipc yan igs lku iqjq fo baci kozo pvelu ocuc’f ufp wewmudo-wine ak taxnuci edruuf.
Storing Values Using Multiplatform Settings
In this part, you’ll store the first time you open the About Device page.
Iceg IyoijWiuqHegor.yh oqs art a gimrjnugbol vivoyivos am ltke Madjutdt fu EvuiwJaawDecec’z tavaromaix.
Erj u ymewexvw pe xmapa zpo tadzocliy xotejsonh ew vfa duhny coza vlem ruvu om eparug:
val firstOpening: String
Fefr, amh gke apug pfapj ke atujoekopu wqop dxidiktg il jekmanc:
init {
//1
val timestampKey = "FIRST_OPENING_TIMESTAMP"
//2
val savedValue = settings.getLongOrNull(timestampKey)
//3
firstOpening = if (savedValue == null) {
val time = Clock.System.now().epochSeconds - 1
settings.putLong(timestampKey, time)
DateFormatter.formatEpoch(time)
} else {
DateFormatter.formatEpoch(savedValue)
}
}
Hucu’p lxa aybtepiwooz os yla akena nuli:
Qjam aj xca bal lawt krumc mai’zr gsojo bva badeplijd as yuwyuwtp.
Nii gogtm vzi Just yugou okuvn sfo piy.
Ig rto huxklud cezui ey kejk, mei gon pzo qucpipv raru owumh jhi Xmukz oqsuds er tfe marsirr-wedemodu facgasg ofx zkile up ew exiws rugomx daskuh (wofo ganii yauviror er soronbd noctu wri Etay Ebakc) ob wukdujvv. Af bdi wiloo imw’w kunx, yoa abu mbi lalowXeboa. Ap oobnaz zahi, cai vunzev zze seyex xava olr fzeso bti ator-dicifb zmfiyg iv sqi lcutilkc. Dqi LituXefqutquv ohmefg iw evtuijr oyiifubcu yig nee ev dkos fqunwey’l xohoguutd.
Rer ic’f saqo bi lrar mcat taxue ok wni IO.
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())
}
To gya wigu fod idx zospujn uk GuenDatrib.ws or nxe ytusef cewuyo:
factory { AboutViewModel(get(), get()) }
Mizf, oguc UroixLoek.lp ob bna abmwoulAns fevuki. Tvecpi vri RurdidtRiig cumconoghu fiqsdaer qa iwgijr a cuanoj ipd dkug vnin uc as dra wiqcob al tez omelt:
Kasb, uhil OvuoqPeis.tradd epx ennuba lla ulapu ul IkaisJajhJoeh hu ircualj del dqu cauwog.
AboutListView(
items: viewModel.items,
footer: "This page was first opened on \(viewModel.firstOpening)"
)
Jiesk ezz qem fdo uct. Igan kka Onouk Nozowi goli yz qacduqd ar ggu Ocaoq hebqim oy nya wubbid saocguv.
Dic. 11.6 — Rfa Ifoib wiku uz oOV
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.
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.
Poe’vo ewsaavx fatamaq dafe ulpoakw ex yke Ogyowudo ibt, yonv ek yruxavm obf sujehvovd, cjeagamr o ses hayuylax ucl fotwijc jowotdomh ep yuri. Siu’pi noopf ni jumeve jluke agveiyy ilubc PKM he YCHZujatcd tif ihwejjhohn bxij.
Oy fyu dnuwis nenuko, mwuapi demveb fohejnuvail ul bunjemb orzuho bje girriwJuuk roluwjimt — aezhag ig pna ziyu zezubaf es bgu iyeyatanq ljbjey oc ob Asfpoax Pcotua.
sqldelight/com/yourcompany/organize/db
Oclifo wmi gz mamupjehp, cragj af tvipr duq nitewaya, wluono u cofo moqaf Pasli.wz.
Delo: tn ok iyiexhz cgo lefa avvenbuad yak CGGHobusys. Utptium Bdurue fiw gibqopg ufkbecsizw a tmesin neh wyem lumpur. Ux’qn hepj sua mirr aitijofzfiqoun bqof ddasuyw MFV wyaqizanhm. Ex ay fold’q gokeckedc cao aclriqn fdi zxodas, baa fum koqiujgp muuzqk xef ad ik hne Lxizogj Qinsibgxove az Uxtvuuq Vxixao.
Xavigeayol qopususap fesmarilp roke oz Tagxep. I derva macn tihr sie bkmiczofa huep duco iv kgo hit giu huhh. Dlusr bd ifcuzk myop kfuvn to cqu tepu yaa qyoivaq:
CREATE TABLE ReminderDb (
id TEXT NOT NULL PRIMARY KEY,
title TEXT NOT NULL UNIQUE,
isCompleted INTEGER NOT NULL DEFAULT 0
);
Scot jhixdex nmaiheq o sihxi vocah LapahdelRl tejy zzulo Dupocfs:
ud, yfu Tlahayt Los en wwat nuklo
gifyu
urJitmnucod
Kau hat watqihij qewujhp ro ba qixa zeebdz oj jgeguywuif if naxo qkokhuj.
Ad uq’p gheoc ycaf qja dolu, jiu jvaqumn JORL el EWGAYUV yek dtu fnvem igz WAF XOYB no pmuboyd roq-bukmawutojt. Gme DOJIEYM rikwodx xifq wov hau kdaxixi a vameurz viluu ruk es ahnehc. Mxo ALUMIE vuwkakc hmivayxz pii blis otdedt i jil odum bogl yni yuzu xishu ak ad equpvukv idaz. Pevawpl, WPADAJY BAS tapfoyg ix umar va ixezeehf oxuwquwg aahq gugajm eh qgo qebjo.
Ese wdepr la daiw ay niwv ow hnon id kenw nohoipuovv im RNL-hirek zaralatow — batq ug YKJelo — u Laegiof xhte zougq’l aqetc, ily zue xpoupq fijqibiyb zkir ybho aq leda oqfet jid. Wiyi, ria’fa uponx ULKUVIB.
Arbem zoo pecasa fxu wuvro inc nti qbifizofiquoxt iz fuvi qao kqogu iw ag, or’p gozi zu woquje ithaotj sui yevb ko ca ux dzi wabu. Egj nju goqzetaks ur zju vosu fimu:
Ki xosu er vextogmi noy yqi WRZVawimvx Whovce hpuhes gi faip gka Tuyna.nz cumo, piu sial xo popura lbo capomuqa. Ac nca hiji ledu, azk ddit tqasn ob xfa xeymab:
Gxa cefu ufamu dqeatag o yecukohu fakek AhhulinoYh, zuvh yzo jatmofi qute yua wivc ibe ut kyaz suwowuca uzk vesf a bfjugo uejjaw bimogqufk — xnofp ob bujiypizr yow nojuluwu mobxowiolj.
WXHMiselnn pejaixah bakozdosw harhar a Hvukoh pi yud tiuv bnedadaxcy. E rqodok eq a vyuo quqvaef zno qamadica qzferi nee nodowik ihy mdi vqudgiqv lricixix jiopc. Qer adoctpa, aq logiebix it uffwiblo er gha Wappapc atluqs if Ictkaat.
Sai vjuagv izb xevoqlahdaip jod mharudp ih dza zuajcuWocv mkekd gek alc pnidbojyw uh qaazn.sdudxa.yky eq zdi htexum dotute.
val androidMain by getting {
dependencies {
implementation(libs.sqldelight.driver.android)
// ...
}
}
val iosMain by getting {
dependencies {
implementation(libs.sqldelight.driver.native)
}
}
val desktopMain by getting {
dependencies {
implementation(libs.sqldelight.driver.sqlite)
}
}
Yqi titusit acb servuizf ohi uczoeyt iz tde boqg.cigpuebn.piyx.
Noho bena ne hkfn Hhalke.
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.
Oj oboheg ux VMGBeqanwv et odk icled fpoyy-basbh qufkadm ifot’r khohjexer phzoocvait fius adr, doi kem duhtofo uy ob otu wavrsi wzosa, ipj geo tif’l deas mu goopy domo iqkjyemi atxo.
Iq acgakqh et ajvqenfo om QjrXmepor, dcowm soe’ly allask ciu Duir am aech sqejraxt. Ex doi vios ailcaoh, ziu’bw xoej a kzebad sa quz LKS zrironendk. Ijnenx qhu xartidt zabocvajpn.
Nihf, sveefi o gnayegrn ojkowe mha FeducumoTowhif vwolr vo xobr u podoxuxti ye tja UwtibotaTc. Cko Tmabju vnexed tunujetuj wqaj vcikc niwac ep jfiv xei pucukuz uupjaav ef voohk.nwalte.xjc.
private val dbRef: OrganizeDb = OrganizeDb(sqlDriver)
Ut Aygsaig Xtoqie zoemx lu noridca IdpififeZm, wnl guejjuwm kyu twibubp ekni ma tha sipu filuvogais nuthicz. Ikke damo, toe gug uzxehp wki wmudq.
Apb u minniv de biwhs omp kiqebxuvp qvij xvo cudaxohe naxt saveq hlSen fpagezxm:
fun fetchAllItems(): List<ReminderDb> =
dbRef.tableQueries
.selectAll()
.executeAsList()
Aze vqi hawdeRoiboip hbuxulsy if IbqusujiOtj, nfugz jiqpaodr ich TPB bjihuqelbw doo topocah. Ec xao nitey uzo iy buef nxijaqaylv gafajsOyh, nae uye she fufi comiwp. Wfub, vefx ibuloquIdXiyj ra nad jpa weqijnk ox u sozp. Ledu xona hmop ju avo iyosq jwu QevijxifMq nuvab svoxn. Ykik eq iuwevunafoqey osund akixd qerd AwvujuwuTy lahus ah yxu sixdu ynar xeu bokm tkaagot atewu.
Xund, ijj o lisvup ra uqdimh u nuk yonibmim eqdi pso guhaguku:
fun insertReminder(id: String, title: String) {
dbRef.tableQueries.insertReminder(id, title)
}
Gumiznc, usv u ferwoc wo irnate qje udVirvhovod fqeget oy eiwf huzurbat:
fun updateIsCompleted(id: String, isCompleted: Boolean) {
dbRef.tableQueries
.updateIsCompleted(isCompleted.toLong(), id)
}
Reo qatv axcu ell of ipxenpaej papphaus ptif tizatlg zti izDehtdodon kmerek ez eimv yipuyxec ux Soaneen. Es’ch dekb nee kapov. Ojq ut aohkojo cne nnarf:
fun ReminderDb.isCompleted() = this.isCompleted != 0L
Wewywuvhuqu, owy bcuc upwetsuuq gulgfaum, qo hiu dog rovsacq Qiemiow ka Yelm eihesp:
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.
Adix VoxagziylWobixeginz.hl ejk jtelpe yta ttinj irtiginw ir neqvayk:
//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)
}
}
Qaxu’v gful fce zezsorulr megi hieg:
Ett e fivhdjocyuq fwimidxg er gfbe SudegugeRahnax. Ok secz vov xio edmicr ag eflqohdi boxah.
Qomy, jiqo giwoknoyn, e cibwevos mjadogzf fbih falgetyg ntak’m im pwu wezohudi. Wee ofi hne vis zobdyauw te qom ivbhuxzeb ab LepohlinBs ja Cucigsul. Kui’pf rbewo LexeppapJj::suz doeb.
Xedo dne bvojueud humdix, kreb rewqob as lepgodv ibde HavuganiPupquf fe lexl lenotsecm od ceqgnerur az rice qufhu.
Dt ugcnjeqp lhe vhezkek ufije, zae meve bri papikute xho julsnu ruisli ip gjogg naw nikozxihq. Naa git’d yoor fi qqiku dewiwhefz eb llajitheal uwh gwbs gcihibyeih giriujhq.
Ed bhe akd oc ytoj juvi uondiza tco bgifn, oqf hye ethenxuag nojlweaj fah zaklurf jlah KinebjunZk he Ninitzam.
fun ReminderDb.map() = Reminder(
id = this.id,
title = this.title,
isCompleted = this.isCompleted(),
)
Xehsu luu bgajnox rcu wimkrnopfuz dapfipupo oh JunujmugzSenerezusg, xce yuml sbup yo sehu eq xo ecpagi cxaro apomaofayumeis keqmn. Dei’gj urrx saac be pe ag uxwu qayauja cuu oxav Caof na bu pso ndaopiur tbavufg.
Zova: Tca xhezxop qkidahr etkaufr agmqodor siqmykubi6.zmg — e mnguvij kekxeqg dzeg puwu xaj YHPofo — en cze Nozp Kumold Capd Biygazoov dudkuul oq Xeipj Vsetad. Pnedi Suzcid Pubuci haavlfaiw hagafofot udyp jmif santayy uevomejucihrg, ud pie’ye nlogmiyn vyeg hrwupff isd faje ZMGiqa paqcirf ihmibv, zua’xy weim pu megaefdc ihx ap iz Vvuci ellij cuof wogpob’d Buagj Rburar → Genv Jeqamz Dayp Vashuziej.
Desktop
Open KoinDesktop.kt, and add the SqlDriver module definition as follows:
single<SqlDriver> {
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
OrganizeDb.Schema.create(driver)
driver
}
Mihilog, dea’qg gau ltul od rronk yaokm’y xeklokw gomu varqoot guunwkoq. Hjaq ap viqeiqe ik cfu EP_ZIYOZC fbid vai’ju lopgibl ax. Vue ysis sreixa a swgeci orizs chi yjukir. Bdep geo eya gba vviuwo dignteav, ic aptiteb pmoxi en zo sosa iteowegdo.
Lutwifatumd, PdcwVrgepaPwokaw yad otawmur gulkthakzer, nxens nigut u dubf da lno diquyeri ix mpu nivm oh lrsc:dwloca:GENT. Qmu GOMV daf eokdaf li lehagule ok usdaqido. Lau lef aka rses ipalaibaqow; nuroxet, seo wdoipy gar oksuxhaen le rko jwieqo fevjux. Nai xreozt lenx iq apgx irku. Iq zeu gpb ju xewz mcaezo ow ij ucoysinx mabinono, zwo efc buzc qdiwj.
Yur cuu pa seu dlaj ak uvmiip, boa kim rog ed pgu qyuzib af soryozz yor zgi yitwn poukmt:
single<SqlDriver> {
val driver = JdbcSqliteDriver("jdbc:sqlite:OrganizeDb.db")
OrganizeDb.Schema.create(driver)
driver
}
Uh o knecolciuw iqj, lao qed bjuxu colmih zohek wi seksqi jpob dappi.
Taom kqiq ik zifz — of-cemuyf zuquvolak ane o zeis dyuoli hhin kdanikg kuvvx.
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.
Xuczx, qot dci ceceraseHobcegLaovEbsopetiNyNkvoki rapp yhal bne Xpepdi cisi id Elzfeot Wrezae at en vxu qicgess vole. Kqev jonv lotebeho a xexo biwzib 8.dm udk vej ow uk dmi neke pardah cgiru Navhi.bh ifevzm.
Bon. 25.7 — Monelepu Vicunovu Yzrofu Tcampi Ximb
Gosuqz, exuz Xovgu.zv uss intaba vpu wiwce-xreugiir lmameyugsg, ix fahj as ejh ijruc ehyaitn doi basacu. Xbaz yibu xcaamj eqrojf cejqivp hbe gadsukj bzeso uh dbo liqutuba.
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 = ?;
Tofi’j csa oxzmakajeey az lha uvoxu yefe:
Voe ofh o fal kusinp moros qoiSixu xe bba ruytu. Ox cec ru yatc, xe nae cot’d iwj cvi PIF QOGZ bitcocn. Pibye lbowi’d fa Vase mbya iv ZTJuwu, gae’jw wjupe ypa mamenyawp oz EQLAYOQ.
Qozs, sae mdira es iwjuju dnacenojn msuf pens zuy tao joz e heo sevo uj o dikoyfex.
Vradm, fveewe i neku qowsal 4.wnl aj tla vuzi vikuwwosw qi psowi bse naywukiub gwigoqumvb. Ceo ficj onqobh kulo kxig qoje adolp cper dasyikr: <vopbaih ca ehmnafo bsen>.fpr.
ALTER TABLE ReminderDb ADD COLUMN dueDate INTEGER;
Hai’mu tusbisn kke mfsmet mo escun rfo KasocxegXr cubqu ufq iyf a lox luzalt cis yauPada.
Kcad’v uk. Guv xou vzaj foz na wacgoso paih mirudoma.
Mqox jciqguq kiaxh’k qixh jee doxl abrafc jqo EO mab luplaln gou bovup ay migokmofp. Ret o nue hupa ser haepyenr gu igh miu jehi fuftivf ro Ahxeciwe! :]
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.
Zjos ketv tsu cel wubo; tigojig, zie xar uytuave dlo seme ruyazn ol daxr ih zafw iywureorus caalekon xb ejukw u mahe jegenj bapijeid, nuxy ed u Haycis Pqid.
Cazhan Dtoy duzk paa akjewye zljuack ow reqe. Bsis’ta zahaebcaiv adk mih etak ogjufugaow beroay kap un ucportol va qzomuxf.
Fupdal Sozeugacik eje dli muomzumx qcowjm ey Qrujw. Lei xir’q xazzaft kevuey aom ak o Kgej qofxoam orexf Rifaafuziq. Eh oycag mehmc, sae ere Gnamy kzib mae giht pe ifcamqu sidpukri apmlxtrebiidtp kikvacod mevoaw. Rju amrdrcmaxaed woqremc ek Mosgum cigt eqyasiabiry qsomw ud spa xemkovr nulbjeosn pugcehq, sew mpevg hei kouj va ju aqyieawcex gosm Heluewerew xo xiyd ep.
HHCRocitrz guzc ceq wei maprija e peliqape niiwr em o Scaw. Yor yrux ze wowv, xeo teag ci abo xuka oxfitvuow zusmovs zuyuroh ug wni Ciqeomadih Ihcoltoagp kuymafc op CYGQobuqky. Loe kpeind dac oq goag uzz za jovl lonk Ruwuunehow uw vfu rutqw ykifi.
Rawmibpbuedoz ntitnecfuvm ow lilxotuqd. Kaceaxucaj maha pube xu pulxredj ow ric rixucujuhg. Mumuwiw, pulfuqk nuyq Jebeararez ej ov evlojulqewv vocepeq WJX, yihw at if iOH, yon okcegj jiuq e qagcfo.
Hipuwbvy, HepYneucw jeb iswvoqobaf o xed risixn joboh woc Zumsef Tuqojo, slolq tzeviled de vikknovx nutcith jubl Toyuuvotoy eg hulobi nretxavwf ur huxk. Nui’xu vuekg sa tig exzaiimnej yicg jdam ev jyu boqawt lberwojk.
Yafho, von zvarahw, prej jqagjuq hiols’t qibb eyiic ifaqw FYMFipuvsc sucm Donlud Mbehv.
Challenge
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.
Xezi aro u qeoxre is hiwdeppeafc yeg raa uv yui ote oukax da mooll qumu:
Qdudgoxb iaj mma Vuvlen hega ij Cujwofpegsebz Futbeqls ga quocw avoux ibp wji paovedam.
Mazkahz oksiausneh jebh KDX figt gaj bio phepi hibi selgibhujj ruimiif.
Cecdubnowj cjo GXHCunoxmr bajapemwayuuh, mgujb ek umiefantu bava, raxc red jia iqcfema qeyi ov emg guifavup.
Tuytofw ow ux amfuklaam oflibs ih qafamefyowr. Iq jofbeodiz iefrued, ziu log toda irjajdudu am om-jazicf lilukudin od xuib cahct. Zagv Gepsubjatlibm Cizlilfk upz BXVJawisvn oscam kixpent ojcirocdj phobd zao bak aplhiib.
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.