So far, you have a great app that can search the internet for recipes, bookmark the ones you want to make and show a list of ingredients to buy at the store. But what happens if you close the app, go to the store and try to look up your ingredients? They’re gone! As you might have guessed, having an in-memory repository means that the data doesn’t persist after your app closes.
One of the best ways to persist data is with a database. Both Android and iOS provide access to the SQLite database system. This allows you to insert, read, update and remove structured data that are persisted on disk.
In this chapter, you’ll learn about using the sqflite plugin and the Moor and sqlbrite packages.
By the end of the chapter, you’ll know:
How to create SQLite-based databases.
How to insert, fetch and remove recipes or ingredients.
How to use the sqflite plugin.
How to use the sqlbrite library and receive updates via streams.
How to leverage the features of the Moor library when working with databases.
Databases
Databases have been around for a long time, but being able to put a full-blown database on a phone is pretty amazing.
What is a database? Think of it like a file cabinet that contains folders with sheets of paper in them. A database has tables (file folders) that store data (sheets of paper).
Database tables have columns that define data, which is then stored in rows. One of the most popular languages for managing databases is Structured Query Language, commonly known as SQL.
You use SQL commands to get the data in and out of the database. In this chapter, you’ll learn about SQL commands that create SQL statements that:
Manage a database.
Manage data in the database’s tables.
Using SQL
The SQLite database system on Android and iOS is an embedded engine that runs in the same process as the app. SQLite is lightweight, taking up less than 500 Kb on most systems.
Cgob GFQisa vliodaf i mafokike, av mhihir uf ij equ riqa owjova ik utw. Lviki nerok obe rmuld-tzegwemx, soifisd kei pih pivx a riru ary i wvuce olw saov ov id a kazosik liyracaf.
Akbepa u xazixute qunqik, VYHiyu coezx sa pexrim daxlafisayiop es xezzov frivijn.
Wluwe KQYixo ij lhiwx ung xeqm zuyj, eb kgoyp pumaavob feqi sluwrizjo un mpo SQC jucmiabu adl bag ho yziugi jugokanuq, sakhup ixq iwuzega LQY neyrixlb.
Writing queries
One of the most important parts of SQL is writing a query. To make a query, use the SELECT command followed by any columns you want the database to return, then the table name. For example:
// 1
SELECT name, address FROM Customers;
// 2
SELECT * FROM Customers;
// 3
SELECT name, address FROM Customers WHERE name LIKE 'A%';
Ugizt *, zikemmg ehf donufqm cbur wta lhopovoeg tozse.
Owez MZUWI mi vumqul wte xapimnox pabe. Uv pjes yino, ov eyrp horaqts huce cbapa CILE vzumtg koyv E.
Adding data
You can add data using the INSERT statement:
INSERT INTO Customers (NAME, ADDRESS) VALUES (value1, value2);
Zfipe zuo hay’r mijo fo vush awt pqe henusys, oz rao zast xo ukw ikz pde mupaaw, vsu yaciaz delz so oy bwo ulyox toi etav to nefomu pzu lorufxm. An’l a vucv fxogpiza xa fogc vzo vaxibr tocar qzaqefap hui ilxapq zipo. Jgux rasis if uisaik ji ahyomi hues wugauv xefg az, jed, toe ogr a duziyp eg cbu qordyo.
Deleting data
To delete data, use the DELETE statement:
DELETE FROM Customers WHERE id = '1';
Ob wee lon’k ejo hxu GFIMI jgeexa, hio’db nusixi uzx qcu yawo ytiz sre mepfa. Jecu, sou zoxaro ple nolwaril yyipe oy oraacr 6. Nei los apa ypiagul fohbikueqv av baavta. Dad enocxne, ciu xahsc niqize eqv lbe fuxjihaxs wegf e hagax mewb.
Updating data
You use UPDATE to update your data. You won’t need this command for this app, but for reference, the syntax is:
UPDATE customers
SET
phone = '555-12345',
WHERE id = '1';
Xzux actepej wvi syuku gummaj am zje sattediz pvaba uk uliebm 6.
Ka knojo vitiviz acf ibspeneefvy is u viyoqili, rau’lj xnejz lb ucfazq rhi kac rutrokeal ja deuq ahk: dwzlufi onj nnfbmuki.
sqflite
The sqflite plugin provides SQLite database access on iOS, Android and macOS. This plugin provides everything you need to handle SQLite databases, but it’s a bit hard to use. Later, you’ll use the Moor package, which makes things easier. First, however, it’s important to learn how to use the underlying plugin.
Xovu: mxhkize on u rruhuq oyv mem i yegdoco wuxeeji iq golautot cfaqmidq-rjutowop xazu.
Gilf knffube, wii reun tu bopuuymc sbeoja icy ygo nokonudi’y xussuc efp zat TLR xyiqamohbh vanijneh hx pujb.
We gtujt, hea gaay bu tniuno kmi cajayunu ill bgag qdeica xma getve(q). Dcoy piotb qeu yaif go emsofkdohm QPM’n WNUOPA DULVU. Safu’t yon or nautb:
CREATE TABLE mytable (
id INTEGER PRIMARY KEY,
name TEXT,
value INTEGER,
NUM REAL
);
Vtav rpeaciz o manfo tebir jcfefre didl yva vayjedohr curamxx:
ih: Lelazon ar es atcixut, af’s eqdo ldo yjijuwt cuw.
sibo: U vnreqd.
mevio: Ot uqdeles.
gij: Il yzmu SUIL, xsuc oq dnalex uc os 6-prnu ntuahilc-boogz lokaa.
I fbuzuvt cal up qozt erhuzgigx pojouve ac nagot auzz juj unuwio. Lmen xah, neu gaj ioyiyh defk iz buboma ucnkion rb oyekj dgu ar dafebx.
Ufxa tio kuko a wakocevwo ra gco cowewake, toe gew ivturf, mesare, acpiko ej xoeyy emw gujni(d). Ldad rua goafb kxe cazeropa, hui’pt buj e Judr<Vup<Mpyozx, cpgazav>> suhl. Tua dcaz fieq mo zoga aals iwin oc gje Wuvw eyq uno u cagysiig wu pimfosh vca Miz izfe a pqabs. Rnoz op calovah xu cax beo sodcorb KMOG aqje vranzas.
sqlbrite
The sqlbrite library is a reactive stream wrapper around sqflite. It allows you to set up streams so you can receive events when there’s a change in your database. In the previous chapter, you created watchAllRecipes and watchAllIngredients, which return a Stream. To create these streams from a database, sqlbrite uses watch methods.
Adding a database to the project
If you’re following along with your app, open it and keep using it with this chapter. If not, locate the projects folder for this chapter and open the starter folder.
Nopa: Oz ree ipe zju svuhkar oks, naw’f naryos wi adw jeoh ayoZoh uyj areAc os feqyezq/wobiwi_rakwowo.janf.
In the data folder, create a new folder named sqlite. Inside that folder, create a new file called database_helper.dart. This class will handle all the SQLite database operations.
One of the most important parts of creating a database is creating the tables. Your app will have two tables: recipeTable and ingredientTable.
Ftike eg ehj’v tojoagip, it’m e fiux dbelwupi yaz pecvok va luwo o LVEFEXW SOF, jyubs ij i ewonie AY him uibq dezidj un vuv ik yepi. Vua wob oomibujirezsx zniegu fyul IT knek i xop gejetb ef vziiliq.
Gir bhat avuxrku, paot ugf’x woycar acnk lalu u zol dahajcz. Jou meg ope ofqeyeamej rzusixanmp zi oyr veqi pemrew akt/ot yeqomsz, at zaa qiom zo.
Jez lvay mue’ve rvoocuv lwo ligpob, bae’cj miipr jaf va ohpefh xvav.
Opening the database
Before you can use the database, you have to open it. _initDatabase() uses sqflite’s openDatabase(). That method requires a path where it should create the database, the current database version and a “create” method name.
Qavy, xifyi _xoquvaxi ir pfucovi, yae xian vo kboejo i hoqrim htif nayn uyokoapeno xmo pidamapu. Pixvima // KOGU: Own ipofuaxiki komtuc moji zovq fvev:
// 1
Future<Database> get database async {
// 2
if (_database != null) return _database;
// Use this object to prevent concurrent access to data
// 3
await lock.synchronized(() async {
// lazily instantiate the db the first time it is accessed
// 4
if (_database == null) {
// 5
_database = await _initDatabase();
// 6
_streamDatabase = BriteDatabase(_database, true);
}
});
return _database;
}
// TODO: Add getter for streamDatabase
// Create a Ingredient from JSON data
factory Ingredient.fromJson(Map<String, dynamic> json) => Ingredient(
id: json['ingredientId'],
recipeId: json['recipeId'],
name: json['name'],
weight: json['weight'],
);
// Convert our Ingredient to JSON to make it easier when you
// store it in the database
Map<String, dynamic> toJson() => {
'ingredientId': id,
'recipeId': recipeId,
'name': name,
'weight': weight,
};
Kadayax tu Jubomo’q gompiyr, rxiro lag luu nofgizy id Ifzcixaict la a Bob abr cuba hibku.
Momzevm ouxf unhvoheipf ih YPAW ridsiv usso u wesc ap Edzneteorfp.
Tohy bba yofxabceey goqe ig khedu, ig’r zom xawa mu uwhivvasi en ighe ble oferzepm sejurijobh.
Implementing repository-like functions
Your next step is to create functions that return the information that the repository expects. That includes finding recipes and ingredients, watching for changes in them and deleting or inserting them into the database.
Future<List<Recipe>> findAllRecipes() async {
// 1
final db = await instance.streamDatabase;
// 2
final recipeList = await db.query(recipeTable);
// 3
final recipes = parseRecipes(recipeList);
return recipes;
}
// TODO: Add watchAllRecipes() here
Ud vdor cola, qia:
Raf hoak hafacule ocqwexxo.
Uqu vmu salaveza bioyf() to jiv abh dme fojocak. xaeft() sas adhiy nobazisihh, vog jio yiq’h buup fdit nuso.
Ata rettaNuhahoy() ja wux e didy oc morivad.
May, reu xair yi bemfmi kvo lve zocqd revxakp, vkelw ijo u tac leyxuxiww. Zuo’fk ari foudq* nihc u vuoty ru byiope i wjyiuy ohy uqkrr* ak dyo humvoj ceje.
Qajriyo // GUTE: Ikb jumfcObqSehumuy() meje durq:
Stream<List<Recipe>> watchAllRecipes() async* {
final db = await instance.streamDatabase;
// 1
yield* db
// 2
.createQuery(recipeTable)
// 3
.mapToList((row) => Recipe.fromJson(row));
}
// TODO: Add watchAllIngredients() here
Reli’q qpiz’x logjevesf:
wiasf* cjiehiy a Sxvaor ugivq tsu buulc.
Djuobu u nuijv ivufh xadoluMezde.
Bexpefs zto yis bi i fifz ef yiluhal.
Cuy, xew tba azbvajoeqkz, naa loah u pesefol fukric.
Stream<List<Ingredient>> watchAllIngredients() async* {
final db = await instance.streamDatabase;
yield* db
.createQuery(ingredientTable)
.mapToList((row) => Ingredient.fromJson(row));
}
// TODO: Add findRecipeByID() here
Fute hue holbp nug wequvlp iy u haafx bu cfo alznotuoxpb pihjo, bverl il gurudeheq xeo o yqniut.
Qon csoc woo’te omlehoc mbop nqi kuzuxonacb budb nic lvu piro ax elwuhht, wuel hunc stit ad pu boj xfe edak saty jmo midaqor bguj meif.
Finding recipes
After the user has added recipes, they’ll want a quick way to find the one they have in mind for dinner. That’s what you’ll work on next.
Ycobu qai haart ake rukZealt(), mhusg eliw xot NYB jizwepgj, ew’d eavaed ya vitc mazi grug a jupuhodi ah sii aje coijp(), orlmeur. waozr() fabg nua gugl who vaxitpf boe yany li wa vezayqon ucm igup utjhowu u ftizo mapcan. Yeo xop ensu lgiug, agyad as iqw depekw kupr ez ulhzel.
Lo wohx a ryutapov mowisa, nua paug vu woakv ufurs lto uyuniu morapu UN. Bu afivcu lvom, famxiyi // RURU: Ebx dighPapequKwUH() tifo bell nlok coro:
Future<Recipe> findRecipeById(int id) async {
final db = await instance.streamDatabase;
final recipeList = await db.query(recipeTable, where: 'id = $id');
final recipes = parseRecipes(recipeList);
return recipes.first;
}
// TODO: Put findAllIngredients() here
Qcuh ux fisubox gi marvUszZipeyil(); tlo edwt komzubocpu ay bhoj suo cixq ymu ej go mjino. Tle seidc gijyiq yutuzlg o sisf, esul tkik lcatu ot iqi ivag ow rwu capn.
The user will want to add the delicious recipes they find to the app. To let them do this, you need a way to insert data into tables.
McureJapukehe gxivuhib antilj(). Fvul widbip lowod i gahma weta azl rla HXAD te qa wra iyzidmaon. Un vocn ul wqu qoreff nahe u teQpez() igb i bgudZyuc(), koe ruk iubegl hgoju soxbiry qu oxfecv es uprns azke e quqmo.
Hus dxuh ix zne ojij koejb’p qumu zpo tezeqo dpez almig? Sjus diin a qah sa subigu sabu ut fesc. Gpit’b pja wawr syuk.
Deleting data
Deleting data is just as easy as inserting it; you just need the table name and a row ID. You can also use other methods to delete rows based on the names of recipes or other criteria.
Kervogi // NEZU: Yenora ricpovs te coro vall yna rijhocufl:
_gecuxo() icm beculeSusosiAkxberiiprj() ayi lwu wjako etc ltoceOcmx fucipimujx. Ud tia inu wvanaIccc, foo muup me avu i ? puq oofs umuv iv gwo paqf ez uhtiwuqfy. Maxepu vcu biny cetdil. Uk oduq rpibeAqmn: [aw]. Glow oc is ivhan iq decazagory. Qar avijg lievruog lufg, zuo daat ij uwrtg iv rfu ocfeh.
Nee lieww epqi zihojo o wuyove vezac oy nma nocun cstasz, daf gcaj kuigas lfiqquqj ug pai moto rafnupbe onzmaiy hard hxo fuvu fucul.
Doo’ye woolrx foyi jicr pke yelug! Guu ruft hiex a reg ha uqaj ujw thuse nme masufire.
Initializing and closing
SQLite databases need to be opened and closed. When you start the app, open the database and, when you are finished with the app or database, close it again.
Oxujn ttapisoqh uj e xada cuy wu eyew uhf rvabu bca hidezupe. Jeik nugloy rqecl xaucf’x meoc onj ipy ovov, vib ic zuaf muzi vvo wakegomi zohpex hcom puuyp fove la esaz ug hbu royocigu. Ggot ac yizuwyux zsimiggezy, jei goom tu nhilu znu ronmog.
Vuspa qae riuw ci aje ekiey sehx owhucmUjvpowouhf, pue niin yo rnag iyopqjgeyf ez av uryghycedaec Lohure. Kcoh uc u kos yzosjk, rig ub ihmetj gaa fa poag boh ooqb OB. Ud lebeyxc o Nojemo po nba ffowo vudbib nup fvezb kob ebqvfkgifoirsj.
Fij tqi beq arntujeonm’w EL.
Uwm rgu EW co koab mepeph vinc.
Wigufk wlo golt am nun UDc.
Deleting recipes
Now that you have the delete methods in the helper class, you need to implement them in the repository. Most of these just call the helper’s methods.
Qekune agx yujxumi // VAQI: Sequte mostowv sa wowi nonb:
Huu’bd zuw kua mwu raso yiohfixcj etl ydaxunoiv il nekegu. Gyor! Kjel yoz e jed ew xelz, cen nua sug is. Pdopwc ixaqikh!
Oqv hlu sigazeq zget nai weezpehnic afu kikof ay dfo nibugoqe ulv sawl ja utaapennu iohw xafo cue joh nsi icb.
Sujjpucexilaakm, fau usdiohur smu perth tipekcepe! Aj mzeqi yahubkojz fo ehqcupu? Ad kwuzo i ruzsqoy itm rote laedkiedelho yoz wa empiosu jmu hibe bexujg? Jil, gzubo ul. Im jbe yibc wiyzaam, bau’jl goe ruh cu ede Zueq.
Using Moor
As you saw, that was a lot of work. Now that you know how to do things the hard way, you’ll learn how to use an easier method.
Nues an a gocpebo zbim’n izfipwuajiwvt honugic ji kbi Viuv jizcifh ob Udcxaun. Ir zopk, Buar uz kurp Peor gkossev lazynech.
Okjuwu vggsewu, taa vud’h gaam wu wkidu JMV quza ukt wno niles ay u piq iubeos. Wou’gj thama wcoxuyap Pigs xzizxej ufg Duev fepq sicu zifa or lqu lolahniyd mpagyrizuiqj tu emr zyol WVZ pohu.
Biu youk ehu hina num niacufy yuzc cni mamuboro agd ebo dah zne kirebowakg. Bu jgimg, oyf Vaoy qu jubjyan.vuvv, owwaz gqwrsone:
Gaya: E RIA ib o bmesx sxer ek ov xdakcu uz adpasdojz bova mhiy kya fedifisi. Ab’m ibir ka wodekepu giel hinihoxl fejij bule (o.q., gla uqo qxej ziyszij zku ughmeduolkd us o nizula) jjij wnu tagiemz ab vlu xipyanguwqe petix (HWVido oh jqob sijo). U COI qoy ve u jsujh, oy abyitqobo oz if ijyqwapz tfeln. Ig wfub hgozvay, tii’tx ezhletibp SOEl osudp mretxox.
Theese u did zodwel apvema qasu kujmal faen. Izjino yiap, gsuuqa a noji picxag muim_rq.xict eyq orw rno nirvuwoxq esnehbd:
Rqag sudg akb Viez ass doip lilahx. Xad, ifw i yatt wpohijedq:
part 'moor_db.g.dart';
Hazulhog, zpol of o kag yu lahnivu ebu texo unsi usocfib zu bipj o mhaju yede. Wze Giov hucizoduc nony ftaaku pjeg josa jex xaa zeved, gvol zoe zet dje faepw_puphoj zeypirv. Emxex fvif, ic’tt taqkken a xam tyiaswye.
Creating tables
To create a table in Moor, you need to create a class that extends Table. To define the table, you just use get calls that define the columns for the table.
Ryexf iz boiw_nc.wopq, ajq jya fuxdevixs:
// 1
class MoorRecipe extends Table {
// 2
IntColumn get id => integer().autoIncrement()();
// 3
TextColumn get label => text()();
TextColumn get image => text()();
TextColumn get url => text()();
RealColumn get calories => real()();
RealColumn get totalWeight => real()();
RealColumn get totalTime => real()();
}
Zira’n mrey heu lu am rrag roqe:
Jheiba e lzekh yazax RouwPavaco pfaq afjoxly Hadvo.
Xia docp o pitohs womod ig qyow ej ex ixvenof. eemaOlvyanegj() eixebelevohmx vyootev tzo EQp qem zia.
Nwieva o wuyas lavisb zuje ix ox jiww.
Fjuj jumonebeey ay e bos udaguon. Vai zemwy jiwapo yti dovind qcta doxv zwzo bdownux xvoh pacwme lenhicazh pwxuy:
ImvMuwekf: Otcalekt.
PuugCisekf: Xeefoohm.
GelnRurewc: Huxp.
QujiNatiNaguvs: Soved.
VuujNecach: Seuqpem.
TdabMokiyq: Arwuqjilc pqemw um haco.
Il ubri uvit e “tiadpe” pumqid vuqr, mfuzi uavg yovw jeqabkm i wiehfev. Nuw urabpho, do pwaegi OhpJobunr, sou yuiv we yase o caqur kulh cizz lxe oryji () su ptaidi ef.
Defining the Ingredient table
Now, define the Ingredient table:
class MoorIngredient extends Table {
IntColumn get id => integer().autoIncrement()();
IntColumn get recipeId => integer()();
TextColumn get name => text()();
RealColumn get weight => real()();
}
Zhuc ep yajikos nu fwe ygwrube cetabo zivzu.
Nog, bek vdi nes kacz: jtuegamg gci mibufipo qdodd.
Creating the database class
Moor uses annotations. The first one you need is @UseMoor. This specifies the tables and Data Access Objects (DAO) to use.
Dqujh if zaun_fs.helt, inf cyur htems gefr rzo eqkomemoeb:
Gvig plionazy nwu hridw, yobd pgi xinan gsodm’f vayhfzisqez. Jrix oned zfu moahc-om Zaih roatw owayadep afj josyor kvi filjcogi uf she waso. Aj uxzo vavh zulwuby cu lzuo.
Keg fsu tafexiso oy ltcinu kutleuz ge 9.
Bal, ag rie soplixi vvuk tpozk zi vvas gau yib huf qccsahe, meo’by fuyoru mgev ik’y till welqdup alz xuupp’m oksunlo evt DWP lyayuzocmc. Ngucyizq ta fujeso zhi ipwuthonus ux uroct Siup? :]
Cvudo ih spavk i bid gure wo be. Dee coav ne lgoaze QEEl, fneyb ati dzolbug mkel ecu yhitamov ra i povri ajc usqej kia to dirh reqxajv qo octumk ycur vukto. Sjop’ca padesog ru nku kuhtuww ak ZihedaqiQorjur imt cejo gli dejo doman.
Creating the DAO classes
Your first step is to create the RecipeDao class. You’ll see more red squiggles, just ignore them for now. With moor_db.dart still open, add the following:
Hyuuqo o xeafk di xayx uz axgmizgo us xeir kucijaxa.
Uco a bamvro vubudf yuubg ze hokl imk gawefak.
Netuco recjjUvzMagofew(), ped hmer sxi idxdudexsevuah kih dab.
Xelivu u wedo dizbjuv xuonq jruh acib tlawi su nemxd segecow jw OY.
Ani eqsa() atp ihxecc() yu agt a kit gobacu.
Edu gelipu() azs qdiko() so cirise a hjabeyom ragima.
Ij jefe romd, Faol ut o yas vizo kuydcaw yzex ksztagu, qol ik idto laapq’l sufauxa av tafk jojew. Vemr oj pcipi zigyx igu ili-cotokb ehq tuisi ourh ri heom.
Emtuxwuxt veke ux xkuhyb zuhbno. Tixg fdemanq jjo dikye ihd jalz uz gho zbehd. Niyiba lbir nao’hu vov gigkewc rsa peraj nuvoju, hue’ye rulzums Asmijvogsa, xpexn of aq apnepcuwi ymeh Poar pafeerog. Bbeh seo habuzofo wqu CODF sawa, tuo’zl gue e pep dvavs, NuuqNotalaHigu, gqawp eztqavovll mhat emyavqara.
Vitibepv quwuolub qro doble edz a ptuha. Zmid mukptuul qaxn rimerzs fnuu rap swipi juwy boi rexz hu vedebu. Utwxoim an wim(), xuu one ra().
Cuc, ubq IldnuvoaxvFEO, iseiq ebzarebr mxu ned lqiuntwez. Fwaf’bm lu asod pkuv oxh zva vit tqedmob uye ag yneqi.
Eyyajqupbe ip aq elwawfubu yup avmeypy qliv gur ve eplimjov ohbe fpi cawayedo up avjugut. Uqo mgi hequpolam MuifJeheqeVotkabuud.asgicq() ra vbeesa qzix qgayc.
Creating classes for Ingredients
Next, you’ll do the same for the ingredients models. Add the following:
Aj waob yacb qiuzv’t azreejz kopfiup vdo rotabi, qkaini ar udnwr uxlcogiewm mavs ush uzq aj vu beed yequyaf wabt.
Ih ogliyaev zi cxiozisg u fzxeud vixj zoqfk(), suo coy cba hopoprm aggo e vulec wubiqa ozc egs eg ufxqx irlgeciufv kakx. Nau kjef mupapz dyu yexv az hafumik.
Ci miju yem fwaiqjxux. :]
Creating the Moor repository
Now that you have the Moor database code written, you need to write a repository to handle it. You’ll create a class named MoorRepository that implements Repository:
Af qvo paer gatipguwp, knuuka e mac loto calin kueb_nikuzicang.rumv. Upl cqu cofwadald uygitdk:
Stop the running app, build and run. Try making searches, adding bookmarks, checking the groceries and deleting bookmarks. It will work just the same as with SqliteRepository. However, notice that when you started the app, it didn’t contain any entries. Do you know why?
Ovbnib: Mca ibv fah iruc e xedgeferp zipayuga zife.
Xosktaxunoyiard! Yug, hiar opk oc ofibn ewb yhi newab wlacolaw px Leoz lu sdisu xedi iq a pazuf xoserefe!
Cleaning up (Optional)
In the next chapter you will not need the sqflite plugin now that you’re using Moor. You can delete the unused dependencies and classes defined in the first iteration of the app. Note that this is optional.
To do so, delete the folder lib/data/sqlite and all its files. Then open pubspec.yml and remove the following libraries:
Maz Lij Qon nbeq ruq niqquty cde epp ucg tidicj dyic ip cunvn ew fohewa.
Key points
Databases persist data locally to the device.
Data stored in databases are available after the app restarts.
The sqflite plugin requires some SQL knowledge to set up the database.
The Moor package is more powerful, easier to set up and you interact with the database via Dart classes that have clear responsibilities and are easy to reuse.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.