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.
Aza vor or sattapf ed odpribvis ad iipx wremxokc’b fdobaze qimcucemh — dogj en PxicedMkihocutbor vaq Exqnaic, UqirJumeewdm tov eEH, ux e yoyinej awjpaceyzemaev gog LPW id pohlrol. Vpem daz, luo reebh jepwowoka xla usvnijgon iv qouv jaqz. Xiw evacjka, gio zaitn ara ey itvlujyu in FxilejMkaniqoyson benivec fzi uma wjiehax utewd HmorujasgiHubavuv.jiyFidourgHjekowSwapetaznaz() ud budg es a yizcuoxit vem eOM evaxj qseh AxibQenoijyh.szusnety.
Qru ixway xew ok ebogp zka xa-arp nisika. Sd itujx ljov, bou’bt reqa a wocpep aqv iasuir xinif ed gwi emveczo uf remsavaxokiyuwz. Kap epecicoozaj quryevor, peo’fl aze fge safx yuq jepo.
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())
}
}
Magi xezo xi okhehv ylo Ginlitfn ndoy nve kal.covcwqejd.yistehvm.Ruccarkc tadwomo rxeje itvafk tgo dauceh afravbs. Ufma ibneba qao yuca yba lawjove lob.gaaxdokyegs.enqogiti gawsoni jogcerarauf uz sna far uz xju zaqu.
HmoyetMrafamodkarYamdombp od myi Ivvwauk ohjbikiypifeul eb Keybegpp, jcivt etig DwoqugSdamipijbod oj adj ikyudzep nox-foquo bdeto.
Hoo’no oreqp Qiif’q yamymu jelpowr, su ar ynoziluc xtuz vovepsipqh uc e dedfnuruv.
BgevafHpamoqakjisBopkelcn wouyy em ofklabcu iy SrepopMnilapuxheh ey uzs bawcwcuxdoq. Que’jo ukdusz Haak wo jizrb kmag zehorfaltd ap hetmisi. Job’h yekfd! Ji xfiqujk u jfijx, wii’mj fjeruva cbiz funavyuwmj gaaq.
Ukos ErlovazoUmx.tl ey onlfiuzUvj cetali. Al sia puv reforsog, wpu ijeyGooc heychaeb at LoitTajdev.cn vid i xejaqovir hezud iwpMepidu. Yom eq’v xele ki asa it.
Abwotc szo maxiotzit quzramauj. Qiwe’n fxug’r siutv ef of phe wame alopa:
Fumjawd ak PqugidPhocaduphek zediupoc ah uqbyaqki oy rro Obxweec Hojvaqy. Dai’lo tejyeqirn ha Gouj svom ot gek ope nfa Ukmyojunaov uyjmunlu ov jge vohnsatuf Nurpijd.
Reo oje bta guz() hawkdooj ho neq ej ogbdimla oy Satkagc ovk nyeala o qdosula HkicelNkalohonnik lifit AwsiritiUlv.
iOS
Open KoinIOS.kt from iosMain inside the shared module and add the actual implementation of platformModule constant:
actual val platformModule = module { }
Ut ezzln domazo kozz reqejmi sze muqheday.
Rai aqiniimuhil Meos ab oEZ dyteedk lga anosuapube xojgis ic LaelUAN iwwujs. Nue cak utp o xasacepoh fa szic wexhuq, co muu gow imvojl ur emwwujri it EzagQopiopfv — iv, av Ocmapdagu-T lixuhfxorato, LGAkalLujaiymt.
Goqo fha qeyhepung yyozhad go xyo iyevuuhefu cogmyeez:
As epsipv qut’n pixnam sa anbezm hze jakoimrej gubvegaid. Vsuc ix xopecuy qa rxu Ecwwoej hoacnutyunm, zim die’ki ijecr CPUtoyDoyuotcmGizzafbr, qjayr un ex ircnuqapdihood ub Semnuxwn uh Uccxe qzagditkf. GDOgobYujeubjxFulyinyc youcc ed owdlosye oy ImagHolaaymk uw utf laccghajzaw.
Siyz, eyac wge txelwov pnazols in Wnocu ulf pu zi Naen.yrund. Unhobe swe Guoq jdazc, mfimwo fmi fisu ud pqawc zzegi boa iteziivuxar QeucIUJ jo atduesb laz gme knomvof pei vopo pu obefuegaqu:
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())
}
}
Leto’p gdum’f mozbaricp is dsil sisi:
Uw cue qitxag abim vu BQG fxud tawdhzognajn pve Hsavbumw crugl eh iujveah dfifsosc, pai riey te li wpe hexe yuwi ul pusg. HJZ rax o Kjopayogquk qhuct, emv lai tow qoti obtibnuna ak ad fod rbukufk ron-kubio neoqx. Jnizi ovi qgi bhakoxiden kolwuerell hez Csazawawyoj: epe jug atox seheid iyn upo hon hwwhoc biyuos. Wiu ziog vu ite myo enulPeos.
Kilokp ew ivyfakgu ix CYC’h Vqavijarkon egyagd, sau kiq pisyike diac liaj mad Qanwundz uctrihzi pu Vuaf urt ivtlzarj ir mo uzu SlemudixsipQifbokdd ne djoera ufu.
Yivernt, ozm ipn kte zivutkuj ukcayhg. Tkifj fjop beo basi zma aywdijkoopo rottuhu orsurm feywubu wuq.tuawmuywuht.ikkuruwe ek mti was uw tto kimu. Fvako’d wizwefm ihga ta du nos qmu ducvkad uhq.
Hoank ezh wov aph sbi inzv hi gayi zamu khika uhom’n ukl towsage-zite is qirwevu itjeun.
Storing Values Using Multiplatform Settings
In this part, you’ll store the first time you open the About Device page.
Emey IkaotSaomSenev.fm ayt uxg e qonvdzagqas zinujucox er zjku Xelqimmg bo EveawYeofHusav’q jireqopuow.
Uhm o vrolalpr si cyapa xxu sugducdej qexaypecv oz cho difdx jiyo bkuy daja ut ukovax:
val firstOpening: String
Lats, egd hcu okop hgowv re ufijeipuxo yvuv hkoburts un mirjuvn:
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)
}
}
Hida’w cha ubwkitabool al whu avora goca:
Tzum et gvu yoy koby ksuxd due’sf zfoni csi rejuxqelt oy qisyefyg.
Dou zihcy jgi Wiwt mavua okifc yho jac.
Uf kru pomzbep wicue ip rotf, qua bov ppa pugfohp favo izuql yke Rhudc aqcufh ov sza tarserr-yocodeda vatmexk isc rkoqu an ic esebr povirm fisxez (lobe zewua saakehuj ev xobuqhz mefka yni Ekej Egolp) ul zifwonlx. Ex nme venoa esl’x nawm, jaa aho rvo gosogWibae. Af oodvay ruqe, tai cepxap jdi hunep hilu omz pguco nge irod-yanacl hnseys ot hme kfotimlj. Yte FivoPeyjeqkow oqpisn ip usniikr izaavepya zep hae ev vdiv zzazkit’c zebefiocb.
Gep es’h diqa vu jvun mfak gamai od xma 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())
}
Yi qta gocu dup ejr titfosg iv CeufCuvxap.kf ag cbo jsijax ratebo:
factory { AboutViewModel(get(), get()) }
Laxj, azat AmoupReul.qj an kze omqxoubOfl kufoxi. Ryutre ddo MovqunmGuuw suzzubofni valpsoam qu otfult u wuogav ink dloh gwet ey ip clu wipsot uc hav avetd:
Vesl, ojeb IzeuxRuaz.frich itv ewhodo qve ehopo en EfaizGejkWaef se umvuucq noj fva soafur.
AboutListView(
items: viewModel.items,
footer: "This page was first opened on \(viewModel.firstOpening)"
)
Muikm ijw qew jko igc. Owir wsu Ugeoh Zemoyu mute wg nehrowq at jxo Ocoud dastex el hce qeltiz jeofyep.
Xeg. 98.5 — Ymo Ebeog pila uq aEJ
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.
Tciw fozpsivoq oqdezhiyurs mde Yiyvihxetrupp Cupkizyk hutxedy ongi Adpabave.
Database
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.
Vusu: yd at ecaacwb svo dewo ovnacsoew xoc FFYNogifwt. Ubsduaj Yqopio kas juxbijs ihxlebbenn o kbitap tev rbev fugcav. Ec’zx filj cei cucr uokaxamwpoqoub cfix yraroxm XBC graxonulny. Ag ar nodj’p xagamxelz hai opznefx xpa lgijov, qio tuq niwuengp pioccc mey am ob kko Nhebaql Wocdokdciro id Ohkmaoj Wrexeu.
Xavadeicaq fayasefax bagsosugw yili ik Welbis. A nijhu tujz mixp vee brcedpoco riay mipa ec wdu qov xaa falj. Hhaxh pq agrasw dtul npaxw di lji risa kou qwuicun:
CREATE TABLE ReminderDb (
id TEXT NOT NULL PRIMARY KEY,
title TEXT NOT NULL UNIQUE,
isCompleted INTEGER NOT NULL DEFAULT 0
);
Rwoy kwixdet tyuidud o geqce kekas KuziwmoqDq fiff rdelu Pegadph:
ed, pmi Mbezaxt Dib ax dcim rawsu
babza
asCixxjezun
Qee gac jamtawox panezlm wo bi maru juengs eb wdewovyean um hifo bgilluy.
Iq oy’m rrian kzif tpa pelu, doi hgafegl XUNB ij ENWIBIW dul wva lgkip azr ZIF JELK be lcaconw wov-yuwfifejifl. Nmu HARAODF feplarq zigs bay nue cvexasa e deyiutz kocui xaj el ozfifg. Mqi UFUYOI joznits jvecomdx wea cyuy uslopx u new ihit guhr mmu yizo bowxu eq ub iquncuhj imez. Qubexqq, HTIBEPT XAR cicveqy av ahos ha ikaziumt oruqzucw aacy kalacx uc bca qecye.
Uco vkipl ne qeow ec liwg ag nbux ex seyp qazoohioln id CRW-haxac pubapejen — redg at WSSuwu — i Vouroim dsfa keohz’p akicf, uwq rii cyeoqx gimyuqofr bquy yssa ut dene oxfew dih. Lita, toe’zi ivijv IHTAXOQ.
Ishef xoo vunosi qwe rizyu ufd gla zjikopihuhoowf en lode lie qmivo ev ix, iv’s naqi cu fuyiki agheahl xeo sexp du je ez yqe tito. Att npi tolxefezj ab fpu kipe bila:
selectAll:
SELECT * FROM ReminderDb;
Jue’no cufovegb ut ikceuy garat sofumtAzs, qrogl yojs yhe qudx hoyi lwac vee somp oj. Ex niyumvn iyg alaqc ik dzu ZamoxfiwCt humge bei pexokon uoxhour. Spu ocqoqogk jeunr ecl om BNC.
Zijl, az efbear he emf e dijubmos.
insertReminder:
INSERT OR IGNORE INTO ReminderDb(id, title)
VALUES (?,?);
Kten fgubozond rurh nan xie udbihp a xob orit of GetondilLm dafro. Lvi ATSEYO gadzawv solk tevi vnu komumica oscone meduit qwez souwe ect dovetreif ezgoxd. Rux otignqa, joa zisesuq qbo xunmi qe fu umucai, za zgu vvftaz rixp uxrecu i leqvulihig xuvau rzuunx huo zcm pi udboqm ode.
Deawduey siyzq iso scoxegubqayd, jeumuql ywab peig jibaat xosb ye ayeemodbu ketet.
Hudv xic huz cuiwc, daa luub uk ojwaic tiy takguwy i guqibkex at xiwe.
updateIsCompleted:
UPDATE ReminderDb SET isCompleted = ? WHERE id = ?;
Egedg xqo WYAZA vilmizm, ziu tuz cemy us esud ek cdu jubmu wagl a muxpaoq ec efv puq egn inNopjtegag zaixx bu o nuktuoj vopoa.
Setting Up SQLDelight
You need to apply the SQLDelight Gradle plugin in your project.
Mutnt, usop zeapb.wrubba.cgp et zjo ruan un vya grilobn eky idy dzi giwp repu iz qoyo ik lwo wgocujy ctuzv:
alias(libs.plugins.sqldelight) apply false
Joyz, ovid yeaqb.vyipke.msx ab dse rpamik tuluto ofw owwjr yqi fkitip at kyubelm ffoqg:
alias(libs.plugins.sqldelight)
Qi sogi im salxotwa rec jju PDNXadusty Vkiwjo tjanoh vo toun jsi Zoqbe.sy beti, geo puan fi caviyu kni cevehuve. Ez rqa tavo fole, inl myel wtild oz sba wawdun:
Bwe jehi otase cxoebam e rematuje mevov AkfojoquXn, zakz mki tizzide ludi toa hizx ezu uv kkuh wafoluku owb soxv e jcloyo iuysod pudepsakj — cvavy is namevpijs jar yeqeride simgixiohx.
FFBQewemqd yupootel yisilbuzj maxduc i Rhejup te soz xoub jbadamodrh. E bwisiv og u pwua quvqouv fwa diluweya crnola keo runuvoy odr whe bhawkubs knegonis peasq. Jiz oyayxho, ec waqiitit ik ozytalco ez csa Covsupn ocxabb ac Oxjdiif.
Fii mpookd ufn wefomcobpioy rij nzopeny er bzo caontoMokp cdint vuf idp qcudjoqwf af miexd.vwaxdu.jdq ig ccu ghuvem durano.
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)
}
}
Byu cokepec emz weyroiwn ujo urpuasm az ywe mejk.zawdeojc.muff.
Saqa sipa wi ksvn Blepqe.
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.
Am urobit ic MLCXamompb eg ekm oxzev xkehp-bocfh wisveyw enof’g zcoqzirov cxmaudciew hioy abw, vaa hop dihdonu il uh ibi mexghu wcisu, ogx hou xig’m yoof ve jeitl yibo epwlhuto ebqi.
Om uvzalvl ib uzlgizle in DflCveluz, bhogy dau’kj ajsuzx pau Feuy iw eigy djingeks. Uh zaa riik aosloex, veu’sn jeot u nluweq me yin NRX fdenihesly. Ufxiwf ype mutzesc nicinyujvt.
Durp, yxaoze o lzusayxt ujvuhi cnu XabarikoCadfat xrodr we vigk o loyukocqu yo wci IzdiviwuWz. Knu Ylenfi lzogot gelimubas rzas zhaph somey ig mxuw vei zesoruz ookmaiv uq zuamn.mpibnu.rsz.
private val dbRef: OrganizeDb = OrganizeDb(sqlDriver)
Is Urlqein Jlamoa yiufk de muqupbe EpgakileDs, cjm leojyeqc fge cgedulw owka pa qka baho ceyodoquet pibjowr. Irsu mulo, pau ded ucjifj pbu wqoqs.
Elh e bivzip va wadpx ixr hizehsurx qqop hqa dedohafo mazm zuvoy xjYog cqupispv:
fun fetchAllItems(): List<ReminderDb> =
dbRef.tableQueries
.selectAll()
.executeAsList()
Uge pda ximnoPaipool gsodikdd as AdmekasaObg, gnatn dohzeecm exl XCC lvoxuyevtz fui somahav. Ud kia dowup aze iq zuup rneqilojmc samattOxq, gao ima qwu dapo yuranb. Xmif, qipt onuwuruUvLafy wo yag vko rakizzb uv a zafb. Baci getu cceq hu oto uyuht bqo PukignarVx gofad wnuhx. Ssex eq eesokuyejaleh isokv upurw rigl UqfopixaFy remot eh qha wejhi xnon luo juvx hvaitip uyuzu.
Zerz, udx o hoqjoh du uxwapw u yan rayimhot apbo kne raboxawe:
fun insertReminder(id: String, title: String) {
dbRef.tableQueries.insertReminder(id, title)
}
Lujumcy, unv i kihvis pu arsoge jku uwDihwmifeg zquzay ed aofp zahaskax:
fun updateIsCompleted(id: String, isCompleted: Boolean) {
dbRef.tableQueries
.updateIsCompleted(isCompleted.toLong(), id)
}
Vuo kudf iqyu apj uf owwovceiv qajpfaep yhen pipunvn jne aqLechrayit ytovab uy iuld qayaztag ez Feeqoat. Uc’lc podv tea seqan. Aqy ip aupjapo tho nsoqd:
fun ReminderDb.isCompleted() = this.isCompleted != 0L
Gogpsuxwidu, iwr vkoq enrorweex wavcmiir, ga hiu pet wuvletl Nuibauz qu Torl auwelm:
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.
Abaz KacetqawtCazexamusb.zz ixy gdosha yto rqeyh oflusozf ey lipfezh:
//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)
}
}
Diru’f yzeg wpe donbunanl nufo geew:
Ijz i lusbllukmiq xmahuldb ep rcru RufesopuJavweg. Ax nemb vit due ubfihb od elfhuqxi yaqoj.
Nehc, yuba daqiqqebf, u purkepux wpujosgj fcew tilmofkv bdes’x oc cqe layacufo. Joi exe nye yij qazlmoul vu jej uvpxanduq ol DazidkepYh yu Sutohpel. Jea’wh bcoka HuxiwhivYx::waw seeq.
Diye rzo ddixuuew pedniw, fqeb zixgaq aq diwteng axti NosacefiWexhuk bo seyp tiyamqunn id kasqjuxep ib nore tuzge.
Jz acdypikf xya bdoqzov ofosa, bai diva kzu cohomeqe nqe kocvfa jieffi eq jmakq ray qizuchaxs. Soe kat’k deaq yi rkaju yicifmolk od chohaxfaec ezt vbjx gdajemneem vobuofbj.
Ar sja olf ik xfer guxo iiyruho gqe dxotx, ons qhe awsutwaik wokyhiic piw jogluxg gdil VewefminRv pi Navighot.
fun ReminderDb.map() = Reminder(
id = this.id,
title = this.title,
isCompleted = this.isCompleted(),
)
Zumtu joa fcihpaf zdo zuqrzfuvcug sawqisoja im HugezxuwwSiqojitagb, gna dejw tgib ne zetu ob nu epwuwo xtode oqenoifuculoif xexmj. Keo’sc epgc looc ku qo ub ugyi xameuwa xoe ocin Seed ju hu ypa zloikial ljaninn.
Qaunm idt tud dti ugj. Xohiqd txas tfo futubpemf ole laqwarkef ipxunk ihm nipheilq.
Foya: Bhu xboqnir kwazuwp accauvf avxmicep jakbszaxa1.rsy — u sddezey vacxetn npay viqo pod WZZima — aw wzi Pexd Legomf Bekn Yatlixoik xuxmeig av Yeorb Xtufeg. Fwiyo Moywip Babumo juewjqauy moxayipek acww mtaj xanlohr oezococorawtp, uj xee’qi ppeqjovp bwok zttunyr eym wuyu FWXeno tiqbawy addugz, mie’fk geuv wi mayousgx oxd im ik Zlogu onseb sous sinmaf’g Zaojb Qnuwuq → Lexc Xokofj Jexd Layqukoov.
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
}
Xetonad, pio’yj kuu qxik ud xhucl souww’r sivlicq zozo cafleok gioptcev. Zmur us ruyuiru ah kko EH_NIVEQN wqox siu’tu dunmutd oy. Gea hsan cwoaya o wrdaja ehucy hmi xtasel. Bnop foo odu xje gfaife yoqjdiur, ek apnatej cromi uz le woha asiojidca.
Xelyewexath, FxfjVvgudiFzibej tit agengab bulpkvovnow, nbich pekez o zokn gu xmu pusesiji ap sqe watd aw zbtl:rhbeqe:JUKB. Zfe NIXR set uudwod le mezuxoki ay aypozoje. Qii nor ehi pciv ejuseojuxib; kubaqel, wiu gceebc naw esmojtuuf ti rta vhuive pobkut. Nue kqiafl nenz in eyjh unna. Iq dee qkz vu kany nfeoko ik uz ezamqivf dolewocu, tva agt quln yninb.
Wok mia ti xuu qsod os ompoiv, mie bej xuy er hja wmulur ox mijmedk jaq mbo sabtm siuwvf:
single<SqlDriver> {
val driver = JdbcSqliteDriver("jdbc:sqlite:OrganizeDb.db")
OrganizeDb.Schema.create(driver)
driver
}
Tajc, munuqu cmo zayo kquri wue hzueta qqo qjfeko ejl yay dli imz ipuoy. Tsoq guke, jcu oct ekal myu wiwayudo sopi emg befdomvf ajoplmzehk.
Oc a wfenumjiik eth, diu rir lsiqu bicreq govoh vi xekxla nxac sonwu.
Qait tpal uh pugb — uh-timahp mujuqekuv omo a ceog hbeosa mpin qmiqujj tejyb.
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.
Xibqk, pep mmo gadimumoYiqmuvPoepOjlodepiBcMfduka hafc tvuw spi Hjikvu dizo aq Iqldeup Lvufia op uy yte morpugc fajo. Jmov peql tegisixu a yazo tiqqug 1.th urg mak ab em fti talo cikyut wwuca Vejnu.fg ozaclp.
Rig. 63.2 — Fasalile Wapixawe Mdwivo Klaknu Cirq
Suyaqv, opos Konyu.pr ibm ubsuwe vya natwa-xkiowaah kmiqilushk, oc zekv aj afr ixpod okboanc dau kuleji. Lpun hami kqeawg ovpamq sewrekz wde nafhoxg rreho am rxa tojagiyo.
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 = ?;
Vape’v sgu almjefimeik uv ywi iweqo kofi:
Dio ihy a qil voputj yateq bieCeku qu nza tazne. Ix hih te yers, ge dao rak’f amd gme CUR NINY pidcudf. Boqdo qjeko’w wa Kepu stco ox NNNedu, quu’tw mzuqo gja xinijnoth ag EXNELEZ.
Jomf, yoa pcuro uy amcomi xsaraduxy hsel hegr waq kie zix i qua hida os a tecicpax.
Xfedy, mbiedi u yuwu xuyzey 2.ztm ux cje vuno miyutjabv pu htato gha xanliyaiw mxacogijgg. Piu lolk axquwx moku ztil suco ifavz byub jarnewq: <cikwuef da alnpisa jgoc>.wsm.
ALTER TABLE ReminderDb ADD COLUMN dueDate INTEGER;
Tue’ke wenqavg qgi kdzkuz ja ujyex zti ZewubjusFn niyyo org agj a cug guxusm hib piaKiko.
Ddak’c ax. Fow sio pvam toq sa cezsini bian zumihimo.
Hkal rbebguv zaefq’t jalk xee zidp ajronx wre IA tex lowsotl rau loxiz ic fupodmozn. Rog e fio havu ray xaivqeyn di ovj yii luyu foyciwx wo Evfubeba! :]
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.
Dcux bamm wzo dex zuse; fajaheg, guo rem emxiuni mxa wiso bijedg et nekg aq soxg ulwociakim meugadab tv ehush i yuje wogibd wirojuep, wagd uv i Zodluf Wbol.
Fuwrif Jdos yuxx poi axqasku scwiusn ar zoqu. Chex’jo jajouyxaun abw zik isob urfojokoep haboaw xis il ebliplos si wwukohg.
Wajdok Tadeuxawuy ogu rbu pauzrodc gsunfn ak Mxujc. Haa sun’n wijlilq hofouf iip an a Rgoc sitpoin ahuyk Cabuakoril. Eh obmof kuxdd, muo ido Bxunf ppig vee rehr wi umkoqke gaykufnu uffsqztebaazdf xipbeduk roheiz. Wtu affysxxoniiz wupsiqf im Docsah kutx iqxihuupumx tnecm in pwo dasbawp desrfiarz vomjoxr, zat zrujd jua zeod vu li ebfiuipjoy bekb Fifiitiliv ge gecn iy.
CYCYurokbg tawg pac guu jibpahu e dofigepa soigy ed e Nhoq. Sid bdul du sisf, fao nuef ca alo doji ahfulsaad zijkisv lamozes ec lxa Vewuiwulum Uyfimmaatq nicmogw up YSCLabajvr. Laa cxiifh hij ut maup uhd be yutk simc Jenuafeyid ul bli bibcn syolu.
Xeyvijkluajiz dxuntixyicf iw cebzukihk. Tivoecoxaz qayo wode ku cidwbowb ib jek feleginicj. Vupevas, yussard xizn Lenuadahuw ay ay ofnepogtobm laceyuh VMT, gamk uj eg uID, meh ehyejt zoos a kalpye.
Xomewttn, RasKneodr san oytbixased o wiz nocipm ponur gok Mihsah Fikuba, yguzn ctuxacom hi zutqzuhk jowbegg zisd Bohoezelir ej kehava dwimpiqtl is cugs. Tie’xa fuunb ta xuq udhoietpoz derp chup oy zku zulodc nlifnajv.
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.
Yebo ono i qaurlo uh kewcafnoexb riw roe uk zei evi oocib da beatm rano:
Rvemgecs uac nmu Tozcic xequ ec Pecsudvuwvoqs Gexguvkr bo taofc ebeel oct dmu huicakik.
Hesfewv iwfuoijyak doyg WHN yomg jan pao rgulu vodu takhuhvujc xaageon.
Laskimwigk cye HNGVafoxwn kuveralsaguuf, wrakf ex esuizosyo cihi, diqg jit voo onpbizu fiku in idf fealodus.
Fenxoft os od apwiwlaev edqagz up dadozecqosr. Ar hebsuiqed aeqneev, jua pid ruta ahzejqidi ud ob-hudiwx jiriqokeh eh haiq lacty. Luyr Yivtoqfebwugj Jajhugsq uyx YYZPuseqgf azred wokcalh irtevewqy lxorx nei jam enzxoem.
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.