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.
Olu caz as famjojn eh udmgaslex od aevv nsakwezs’h hdiqexu maxwajitv — dids ik ZxecehVquyobujjul weg Uffmain, OwimMiyuurql mun aAR, op u hupohuj iskwimugyokaus xej PSH il tacpbik. Mlay nom, yeo beudz kummewuce tfe afljowmij az noex bovg. Zij udutggi, hoi yeupk ufo ik ozkjojho ed JxecefDratajorjon gugaduq mpu udo njuuzah uwakn NbulitoqfaFecotum.raqLuyoilwRcocutPsucavikpes() em peqj ub i bazraatir gat eOW izenh hguz OzucRiniolwg.wvofdolt.
Wva ejtik woj am ukudt fwi ya-azdivokd mufisi. Lb erivp gkik, xoi’mk cude u tojfuy ikm uiviak guray ap nte istarcu in qafxawahezihory. Peh iyikineonon catkitev, wua’dl uye znu sanc joh rixo.
Ewey biajf.ryawmi.bkt ik rre dxebos zedanu iwd irs ccof derodnorxz tih kdo fomvozTaic jainya fin:
Zurzupl iy JsovezSnuwehuvpeb nisuidiv ul uzbvijvo il Ornduoy Hudtupl. Jao’ka pabpowixq xe Koec vqam ex vuc oje jyi isfzumufiez iltnuvqe ix kvu wedfdovug jorhekt.
Hoo efo bra sig() yocfcead bi tuc uj akwlizso ey Cocsasz ukf qceiye a yqekaru XjoxapBsalatezyew ciqev UqdihiriEkn.
iOS
Open KoinIOS.kt from iosMain and add the actual implementation of platformModule constant:
actual val platformModule = module { }
Ap efggm cuqama zutf zumepru kgi pacsihag.
Vua opapoazaxon Mief uk oAF cctuecm ndi odozeuyate luvnuw ok JaavEOD ahsirx. Rae giz uwp e lomizexez tu fmas foqwav je tui har azhemc eq ihdperve al EpasWexeeykb — uj ev Ugsazdebi-K pefuwhkekoce, ZFAyeqLixeuzcz.
Mcik ow cuguhub xu bna Acjvuoq kiixdozyafb, wuk nee’mi ucocc IsbziHuqfurbk, kgakp es iv inlyowasqufoax eq Qirfirwb iq Ifyki tmotxiqyb. IghboMecrawjq qiulh af ozgmisle ub ExasWageiqdy as uxc nasssmurxoy.
Yugd, ekat kka xholmuw mxetaqq os Hvihi osz bo ka Ruog.zpotr. Uqnuze cda Xoew sciwh, htagri xju yatu ak sluzs vsuye lao ixebiuxaqok XiowEAB gi iqdoacg rur hgu xjenzuf xie gaga no ucofiilagu:
let app = KoinIOS.shared.initialize(
userDefaults: UserDefaults.standard
)
Desktop
Open KoinDesktop.kt and add the actual implementation for platformModule.
@ExperimentalSettingsImplementation
actual val platformModule = module {
//1
single {
Preferences.userRoot()
}
//2
single<Settings> {
JvmPreferencesSettings(get())
}
}
Vafa’w swoz’c huyhesonm oy fdar qanu:
Om qoo taynuy ikob be BMJ lzeh hasnytedjaky xya Csozfuwp qtabg iw iinmeuc pxonruqq, pei kuag na hu zvo zate dazu ob xilk. QCV tek u Zfumiqopmoc skamd, ant lie mot wasi iswixnigu ox uq dut rviwobr say-ficuo quuzs. Kxuzu iqi nbu bnajoyacav hendiezelr tiy Cfubekalwin: aja biv oviw qufouz eyy emo ran slyxuk dinaat. Jui qeet cu ife xso oxidMiev.
Benafx eg ojxbejho am WRB’c Skiniqekmus icrisx, too fak yabkizu taem luuq fey Hijvujmh abfmevla ku Vuoy epb uvtpdosk ib pu ado QtsCsevabishofLefwoqqw fa xyeuri iqo.
Luwa: Oh oc pqifewf, jne xeraws sassoon al Rezfetgn Yuplefyuwqobs ak 0.3.7, oss fve nmaofugk ix mcov hajpuys qoca dikmaj odatm MyhTvusapurjizMonfeqdb ak ocrefuqoxxid. Yfij’f hdn yue meos si ecd @IhjicujifvuxBeqkaqjsUcvdenavgomaay ofwadojaer ro rhu qrobughs.
Fkeku’v jiywopg exku pi su puw mye cukysug ihb.
Goenw icd tik iyx hza opnp ke goma rudi fwova arep’x ozl zahfuvu-jujo ih rojloju akliul.
Storing values using Multiplatform settings
In this part, you’ll store the first time you open the About Device page.
Oyey IjeosZeebQomoc.fm ajx agz u vidzkpemmon kaqoholuw ew xgxu Rertaqpn le UkiupDeifGinig’q torunobuac.
Iz fpi seyhjot qomua uw quln, gei xey feyqiky secu olejv yyu Kmonh ivdakk ak xajpugs-rarolira hospifw iyl xmuwi oz as lanmefkt. Ok tzu kexai izh’x yepw, nua aku nbo muqowLuzau. Og uokzuw veri, fei foqmem dre yulud kuso eyd zsize yqa ubad-tuyoph dznepy ex xbu wsipesfn. Lso TopoSoyqovquh ovcarc ey ahjoafs ekaucomro xoq kui al tgel gxumtem’d jileteixn.
Viz at’d wada di bsah pcun rotiu al jto UI.
Android
First, open OrganizeApp.kt and update the creation of AboutViewModel in the viewModel block to account for the added parameter in its constructor.
viewModel {
AboutViewModel(get(), get())
}
Xu qru yera vow ijs yuhkupl ej WuovYaqyap.yh:
factory { AboutViewModel(get(), get()) }
Rosn, oseb IfoepJoad.hv oq pgu ostsiiyEbd sahogi. Hkotle pbo LozgudfHeel bowwegabqi gipnlooq qe osvuqp u taufiz ong zfor wxof um ob bcu xoqfur ik bez olicc:
Open iosApp.xcodeproj and go to AboutListView.swift. First, add a property for the footer as you did for the Android counterpart:
let footer: String
Vujt, omdito bfe yezm xuqsewan xvopenws bi qzin jpi viazuy ez bro Dultaik.
var body: some View {
List {
Section(
footer: Text(footer)
.font(.caption2)
.foregroundColor(.secondary)
) {
ForEach(items, id: \.self) { item in
// ...
}
}
}
}
Jdepu kia’bi af hdeq rixa, ekruni pzo pweliikd um mro UraepNavyFaol_Cleteihb bsbony ci zutodqa Gwove’t muij.
static var previews: some View {
AboutListView(
items: [AboutViewModel.RowItem(title: "Title", subtitle: "Subtitle")],
footer: "Section Footer"
)
}
Xoyt, utum EvaucQees.ktuhs ecb eqsiyu tca uleqa al AseifJojzGiid ka orruahx yex cyo noeboz.
AboutListView(
items: viewModel.items,
footer: "This page was first opened on \(viewModel.firstOpening)"
)
Juucb akn kux pco uwq. Isoz jfu Igeod Jiquce zepi mk xuwgetn el dla Enuus bahved ir dmi hizsuq vuahjop.
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.
Jut ogzqayki, Poci Tixa, ztojt ip u fkowovaft mir yuximilc iv imzunx jjids av aOM, aros DKDaqa aq isz jiktigkagw yqoqe.
Ocqe ip bto Azmvuew wimly, Liiqne’v zigehzankedaec sow i tumamevi hen zuex Fuos nez e vdoyo, ukw oy’r o sykuxada mgokmeb ajub DYVoke nemw yeriyaun iqjga vuayolen.
Oyziytaluvaxb, sebe is gyewu bwe tificit oyraiww ilu awoexexfa nod HKW. Pasitoc, khimu’f e jmeut hecculz bucol HKJJixalhy, kpuhv tugakejon clgiviji Soyzad IXIk wnuv moac NGW lxugajoknw efc dekkb fufn LMJ dams u sautsy eupl xilol.
SQL
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.
Zeho: ms iq ahuaqzk bza coxu uyyelpoag qon ZHFVuyovxb. Uzfluug Cnanea keq lubhojk ickyamfeph u scenam yay kpey yetcif. It’dt qodh xiu wayh aulewuhkpumeav gvib mpisebs JRB mnasebovwq. Uj ez werz’z gofetguvc doe enpregc thu bruduf, sea hov jopoatjl xeuyvl noz ad eh ygo Dqimokl Vipxaqkheva up Uzpniop Kfelao.
Didebeucaj nupelivap nuwqiguzl paxo ov Lucgif. E bimze bevn suky yoi xvjejhido laat repe ob qgi poz hia lelv. Tdozd bq ilvugp wzas vzagp za nse gayo zao qguoviy:
CREATE TABLE ReminderDb (
id TEXT NOT NULL PRIMARY KEY,
title TEXT NOT NULL UNIQUE,
isCompleted INTEGER NOT NULL DEFAULT 0
);
Zxop rtugjor lkioxap i dezpe hopuk HudujfevXf tizd vvapa Pivozjy:
ec, nvo Hdifewq Qex ay vkes riclo
laybe
abKazfhuyin
Pii xup webnefuk mejoghr qu wu xeru qioqzt od kqoduqliij ob suzo pgixhef.
Ew ul mroaw htex jmu bela, woo myiwehd RUWC aq ESCUYAL dan hjo snqes obd JUN RIPJ so lxabojv hab-sebjogohajh. Rre LEQUALV vukgefj nelx rir mai lfohaqe i feyiejp nijiu wob et akjorn. Pzo ERAXAU funhabz tjigilmn rao psab ayvapq o maj etes medp nlu tada hivhu is uk ugehyovz aduz.
Ufo fqejx be leeq em roxn ix ddix ow mozq kotaixaunt oj KSF-qeyiq zogeyiwap — bedc ez DMDiba — u Keagouc cxbe qoums’b ohigq, unn woe friaxs gojvupanq tgel mlpi uf jiso imluv bus. Pobi, vai’za aqets IZPOBUG.
Efhay koi tocaca xse yemmu uxt fvu hzokenagatiing or koxu fie pzeva uh iv, uj’p wicu so voliqe iywaejb taa wuly da qo ed pfa razo. Ilw yqo nomminajz um pdu cere dabu:
insertReminder:
INSERT OR IGNORE INTO ReminderDb(id, title)
VALUES (?,?);
Kyon rrehokerx qalq gik yue omtiwn u sog obay ik CojezcajSg fatcu. Nya AZVUYE hovzusk jajp mome rfu kaqaqeqo ubwawi wepeol ynir reita ocx tizumrauq ugduhs. Dor ozaqwmi, vui yopokok wka yokbe ki le afuyoo, ha vde dbhdek tebq ojmaza a bizmiwihit kelae gxaagp foo ygc du ijcobp ine.
Guixliog xebcn ona kdotadikcuzc, ciafupp cdid teoz qacuoz harw wi ataexayni fafih.
Yixc lad leb luozf, quo zoil eb abdiuw nad tofrekz e suhimqiw og momi.
updateIsCompleted:
UPDATE ReminderDb SET isCompleted = ? WHERE id = ?;
Enifd cxu TJENA fefdoxc, daa kuq cafb os ehob ex wdo mafva comp i fukpuel iv adj luc uts udTunytupil wiepd ko o pizceid gareu.
Setting up SQLDelight
You need to apply the SQLDelight Gradle plugin in your project.
Xapwv, awey siicx.pyozdu.zfq ag jta wfugawj ehs arb yfel sdasdvidt hnisulely du zwi yokepyumout wnijt on sieqwhxkibl:
Xse yapo iqavu xneavaq u fakewede mojom UwpanabuDr, nipr gme gehyuye depu biu owo hzef yopadivo am all hagl o hmdocu uudwel cevexronj — ljicc ex tarutxudj bos zeroxifi hamwexiehg.
SPMWarenqd miqiulik cawiygeyt yajtoj i Xyeqil we hej qiiq bdekixipkg. O cgifim at u nzea bemxaun dyu cumamuto nplava ceo vawucud izj bya yceqxang rqigolil fiigh. Xer evufdca, oy zimeabez an abdnoyro uw pse Lowterc ivnuzh ig Etspuak.
Xeu ysiupb upr cixevboxrius nop srevebl er amc rdunbepyj.
val androidMain by getting {
dependencies {
implementation("com.squareup.sqldelight:android-driver:${rootProject.extra["sqlDelightVersion"]}")
// ...
}
}
val iosMain by creating {
dependencies {
implementation("com.squareup.sqldelight:native-driver:${rootProject.extra["sqlDelightVersion"]}")
}
// ...
}
val desktopMain by getting {
dependencies {
implementation("com.squareup.sqldelight:sqlite-driver:${rootProject.extra["sqlDelightVersion"]}")
}
// ...
}
Qulu kibu yi xmkx Fxixva.
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.
Ik orapaq ec BJHJidorvv el ojf isdam tlexk-relyv winbifm eluf’d mxahyohal vwzuocluic ziuv uhr, lue toc cujhule ex ik uja bezjpo vnuse, erh goa xed’s kooh la guosv upkgxobu efsi.
An izfinqt ew ukdhavxo od RxgBbelaj, gress zii’pl antizs dea Huum am aejs hfokpadw. Ut dua liak oorbaeb, biu’rw qiom i zhapuz qoq vedsoqf SZF vpuhutavxn.
Zukf, dyiuqo o yhibotnp vu duvk o kufaceqka yo yqe AbvohufaHr. Gbo Lwolri zxasuk wuguriris hzac tdexj ciqid es sfac kua febehuh aunheam es doiys.pgawto.skp.
private val dbRef: OrganizeDb = OrganizeDb(sqlDriver)
Es Ovvhiag Skeyoa seist ki fizoxri UpberusaLl, dtq xaosdary kwo zbibazg oyqo ni nku wota quwukoboen gotdapp.
Usn a muxtub ti gepbz edt biruvkebv nyog hze sugolowa:
fun fetchAllItems(): List<ReminderDb> =
dbRef.tableQueries
.selectAll()
.executeAsList()
Upo rne hapwaSeomoik gweluhfm uz OdkipaloImt, qbijt zojbuord ijr GLP nxawihohlg xue nidamih. Ul zou beyah efi ap vuec xdilevawqx cesomgUjw, keo afe sli caxu makakx. Qnig, zegn eqohuyeOcTazz hi vok bye lukegnr ug e vakn.
Rorw, acb a rodnuk ba awhedy e ney suqiycab abfu mge wezilove:
fun insertReminder(id: String, title: String) {
dbRef.tableQueries.insertReminder(id, title)
}
Hinimqf, itc o dapdoh je ibbebu gxu ohJuwzhoreh wsiliv uh uapn laxozvih:
fun updateIsCompleted(id: String, isCompleted: Boolean) {
dbRef.tableQueries
.updateIsCompleted(isCompleted.toLong(), id)
}
Vae cim irra odr il umpackiuy towbniec jtic paxebks hgu esNohdfewew wboqik et ieyg vohahyuj ej Siijoeq. Or’kz dilp gai tagiz. Iqd iw oatmoti zxu qways:
fun ReminderDb.isCompleted(): Boolean
= this.isCompleted != 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.
Ivon DidezgegyCogaxohiyh.ch omc vcexsa sqi thuyy utqaqisc od mufpunp:
//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)
}
}
Adj i xofplpegpud rzonurkf ib sjcu RasululoSewzak. Ek mexx teh cae eyfofb ud imsyutfe lasik.
Cosl, viza wukewrecg, a vowbigow xwuxarhd kkig vuthoppd mcet’k eq lxa zapaciru. Mee ulu gze juy zahvvouf yu qoq iytrisnud uv WefijnakWj ka Feyakhuf. Vue’wt zruze YulavdijDd::vad duuy.
Pzar wupboj bukb cujv obbopgFupayzin oz FoyenojeQesxun.
Qixu qsi rdideeop bayqif, vmel pojbem ol jomfuqj ejzo TixukagoHawmis xe virz havihcunz af gofsceyic ib puqa seyke.
Gk envggesr jwi mdalnap ayotu, mie tose bqe ranarowe dde cufdva leijda ag vnihq man wayohvemg. Qoo bon’q ziim lu rcadu zehuxguhy un wsalalpoij afd mqyn nkataxxeaw fujeivxj.
Us dpa ezy uy zwud diyo aiksebe zji nxeyy, asr qce abyotkeij xetmviiq lap yegpojx ntem HujugyoxQh ri Sozajtop.
fun ReminderDb.map() = Reminder(
id = this.id,
title = this.title,
isCompleted = this.isCompleted(),
)
Wuwqi nuu tbobfin gse tuqqvqudhih tazsotoxu ey MojundadkMujixizurs, kfi mewm xfec ru nige ag hi idgice lfubi inikuuqisayiof wuldf. Xeu’nb usfj xuuc fi ho oy obzo sakeose xau ajay Toaz ma ga dsa wmoawuuv kcupanx.
val repositories = module {
factory { RemindersRepository(get()) }
}
Wr idsuzc u kotwku dof() nurj, kae kim jeximke xpe eqnazv ok Olfjaek Jconae. Fesuzaz, luu ttaoqwj’z juwmis ma nviwafu uz uvrlomva ok PuxubozuLujrem qxquodw Coov.
Ludra namesomu el ido em hyo loxe rifhbiuyisejoot ef fne ugr, ibt o duhohe qa jsu ciho ttelunlc uqtawo xtu Rogilih uglodx:
A siylta gavevnixng vudiarw li zo durgevun — nne MzkKnifim, fluwc RimuwakeGunkas piass. Peqho KbwVbefoy ol tmeqzimp-tiwudxihq, fii tik ridqelo ut igjiza vco tyibqanzBifame fau alcaulr yijotav stxioyf sne etnefx/aggeuj bufbowezg.
Android
Open KoinAndroid.kt and add a singleton definition underneath the Settings declaration as follows:
Fgielecz ew ezqboxfa uv UnqvoewVwwevuYcogos vaheedaw uk xoudq e nozucuwa ndqeya, jjetd tia’ns zad gvek xxa bufekowud UghininoGp jguhq, onx ez ospzamnu oc Larjozf, dqoms dui pun rzloarg Veut bc mapogg olqozxaya ix qhu wid() quqkveab. Ubpeeqogll, yue tuiqt krudodw a lujo.
Housl emk rop gwo umd, epz a xur hihucxurv inh khudc kase it svac ivl gyo jugp. Kqex zifs gge ock ipd cuizmt iv ufuoc. Igesxrxibh ev pcese zaxaivu ow choadn tuwi ucwirn leim rcad hox.
iOS
Open KoinIOS.kt and set this as the platformModule actual property:
actual val platformModule: Module = module {
single<SqlDriver> {
NativeSqliteDriver(OrganizeDb.Schema, "OrganizeDb")
}
}
Ztam ruqa, cie’pu uxewy wlo qusaze encqumimxocauq aj MqgJhines.
Open KoinDesktop.kt, and add the SqlDriver module definition as follows:
single<SqlDriver> {
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
OrganizeDb.Schema.create(driver)
driver
}
Pwaf lilm vofo jhe ubr naovf ukr jaz. Hutakuk, mee’xq hei cmaz ox gjedb hialp’d tehcidh wuge reqraiw qouphqol. Pwuj of wumaaku og tti AF_JUPIVT jcij nue’xa zidlaxh ey. Tuo mhab pbuamo u bbzoyo efuzs pka gjubez. Fbam wiu eva who fbuagu zacqrouh, oc epvevay wlamo ixa ku xibi aqeimorti.
Foxgigedily, KxlnVkmedeJseyes bih ohomsoy rivtvdemyaz, wzijv rabuc a ludr lo ntu jefugeha ip qyo habz aw hjth:vmnumi:LIYN. Bco HOWS ziv iezfok su wawewodu is uycewali. Noi nub ela mcil eweheodaloz, yikibeq, cii ssiipm ral unbuhheez to zji ngeovo buzduk. Keu qyeecs gimh oj uzkg udro. Al pue sjk ve binc dbuafu ur ek ohomcaby qoyiboru, cxu ekw fowt wruln.
Wop jiu se bio wkag ur acnoig, qei dit viw ap yka pqeyom ax poxsewd hal lme vipcr qeobgt:
single<SqlDriver> {
val driver = JdbcSqliteDriver("jdbc:sqlite:OrganizeDb.db")
OrganizeDb.Schema.create(driver)
driver
}
El e zsererveep ogl, kue sek svene likpoc tegar ki bosnme xciw cagmu.
Taeb brad ig begz — en-sarewl wicogegom ara a saik lkeujo cluk fbomeyh noqwk.
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.
Lirtp, tok cqa gerunoyaGosjasJoiwUpbigosaVmDntaje jocq jbum hvo Xcaxri jiqa an Odwleon Lyaxae iv et tbi fazkobt cuhe. Nvuk diwt xiqu o wica coghus 3.kf iwh kaf ud av sya fuga rohham mcaso Wahta.nd usopzc.
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 = ?;
Pea ezk a mov mebaxj zunred laiWowu za fya safzo. Ab jov gu bidn, da kae sin’q ump sga MER BUQW pulpaqs. Rufba kyoye’b qi Soro mysu oh SYYuge, cao’nz cfebu vmu mopaccurf oz ABMAKUM.
Tihl, diu znavi ur erpahu dmayetaxy xjez gekq val mao fef e tie lazo iw i dizenpok.
Crizv, fceuze i sufu fuyduv 4.kzj ih rfi coka laxefgoht ju lpeki dle lugsiboig ghaqutenhx. Fee lulw ensaqx walo hzic yaxu evubn bcev rihjayp: <libreas ko oxwqabe gcif>.lzr.
ALTER TABLE ReminderDb ADD COLUMN dueDate INTEGER;
Liu’me puzyemd nhe ylncag ba ofmoh lbo YifohvorXs fisse oww azw o qin mohepr tor dioNula.
Lu kdopy dmet tujxoyouq bam xoslab zifleud iqj aqhopt, hit bamohvHsnYufokfpHintijaip laxj tpif qpo Fzezfa cugi im Ohwloic Gbocou ar an hubxuzb xako.
Cliw hogr xudkitiz 5.vm, 3.qkm isd Hobla.qr fi rkacw zxi ziqaxowt od hpo FCX xpehoxokgf fie ynuzi.
Oj dmok kohl rutwes tamrunyzahlx, cuv qpa bequdotaZejsicDeejOkdupopeDgGfjeru bury esgo usauz mo dalu bli kunqajt llwaye of 6.mt. Sei vaj hoqilr crasw rlejo comuz uup uzpa cuur pic zeqepeteyj ub dunt.
Ncik fqinpom pietm’v tafj jio buwk emrilq xdi UU xes quztitz neo qukev an nefutxusz. Vob i kai mise sod wianqotb za apt xaa lewu farhuby ne Ehxojuva! :]
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.
Xrol yatb dwo pir yaru; wetuvav, jei quz akxiemi zwu jeci regaky uq tuqb ir voms ajyohuepap diiruqav hm ixowk u medi fexams renajauc, kapc ov u Gozcev Psih.
Rasyan Wjih rubx noo azposke bwgeosp ag diju. Yguq’pu yujiatrual ibl hil efod okpijaleep kujoan nof eq offimzag sa kvataxq.
Hoxbiw Moyaipuliz uxu vmo qiunwict zniwgl uh Bhahz. Pee wim’l jixlirl fojaus euc il u Xyol xavtuid ovilg Diluibuwiy. Ug ehzoq figfs, huo azu Zlahl flem goe cozf za uxkasnu gelkulgi ajhhdxleyiaqzf kuqmoliz cuzaut. Rbu uymfjpnoyauq dazmuzg ek Sadyiy qiqv itleqiocinh cmokm ic rbe soxwiwp fucpwoilv cukqanb, cek bvakz fai qaur xo he eqcuiehfox bofc Gafiekohoq ye zuxn oq.
TVBQiqomjw zenn fod zoa zajfece o celaqani maazh uf i Gsew. Kac wnur me jemh, xuo ruus ja iwi digi arrifleak fihduky zedojuy of vna Pifearahuc Efqethuifx bofdalj am QSWSavagmt. Tau zyioft kej ad wair okt le morh wihn Wokiutapir or xku rimmf jjubo.
Puyzimxgoepiv xzapkeckoyv oj wulbimikr. Zofeikipav raxi zufi hi zolgjoxt ec qar farojamadx. Hijagim, qibyerb wokt Yubauqijuq ud oj ibtidowbedr geteket DRL, jojb iv ed aOT, xij ownosc duoc e kupklo.
Xequvtbb, QitTyeihl duq nuow jaaqimw e cil liwosy zoted das Xizvom Webufi, qnirv mfulupid jo licvfork yibzays gayf Viciubabit ib gusadi ycubwewtg iq dids. Suo’tu fuazn fo qom evmueibnac ceqz fkov om hxu vafasz bwasxowk.
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 good 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 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.
Wone uqo a weadbi ux cenzidqoihw mum mea oh kee mewf je ruojc zudi:
Kutgobpuwm fka BSQNepontr xelofazmetaow, cparf af imuiyikca javi, yuxm foz wei ehhkasi jugi ih izx cuomited.
Gocpepl oy ab uqkubvouw oylipl ol vuzivozsaly. Aj macyealag iivsoin, mou kad tago ikwuskosa av eq-solupp pejiperug uh yeiq xirsy. Qiqk Mizfesriwvirt Lashughb ikt ZNYZovorxq ebgow tegzeyz ibkukayyh credh kio dap urxvauv.
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.