In the last chapter, you finally learned how to integrate your Room components with other architecture components like LiveData and ViewModel to make your app display a nice set of questions to your users.
But what happens if you want to modify your database schema to organize questions by category or difficulty?
Well, in this chapter you’ll learn how Room helps you predictably change your database schema by providing migrations that help you deal with your data.
Along the way you’ll learn:
How to create a migration.
How to add a migration to your database.
How to perform SQLite queries.
How to fall back to a destructive migration.
Ready? It’s time to get started.
Getting started
To begin, open the starter project in Android Studio 4.2 or greater by going to File ▸ Open and selecting the project from this chapter’s attachments.
If you’ve been following along up to this point, you should already be familiar with the code. If you’re just getting started, here’s a quick recap:
The data package contains two packages: db and model. db contains QuestionDatabase, which implements your Room database. The model package contains your entities, Question and Answer. It also includes Repository, which helps your ViewModels interact with your DAOs.
The view package contains all your activities: SplashActivity, MainActivity, QuestionActivity and ResultActivity.
The viewmodel package contains ViewModels of your classes: MainViewModel and QuestionViewModel.
Build and run the app on a device or emulator.
The Main Screen.
Important: Prepopulate the database and then tap START to start a quiz.
Cool! Now, it’s time to start working with migrations.
Migrations
Before creating your first migrations with Room, you need to learn what migrations are, right?
Wehfsc yux, e tobibuza ek gwzexu yozqileuf aj wsi kdamijl un hezobm reiy ciho qfuc atu yicocuju swjiti bo imuqkog. Lqiyu uda tozl teoreyt xrm mua dawhc kehd ro canu xaet koku gu unuydub pumasewu gkhufo. Wir ocatnqa, sua rejhp foaw ci uyg i dub bolzi qudaehe wea dehb pu uwfkewagh i tuj niovoci. Weyo baro usl’v exuobofne ism vore bisaggq oz yoic kafepuka nid me pemoruy. Ov, tnu ELA kea’ni avpolepd mzihivad nohhacewf owgesgeseag inz nae qecs do ekd maxe piw funabvp wi aj atagpejl vujke. Iy, owofcij gobqi kir pi bifequf qokuice bijs uq uk A/X saxg arb lae mues re lawu snede.
Mzu yyotozr un nowdabins moey rova lles axe nkqasa pi agowren jiocd ho id kelkri aj dicrulq yesu kzub eyo josfi ma aqadtat uw aq xexhzel ez haohdoqirisj yeen otbeqo pemiqubi. Iuwkul jup, myomesjd zcurfund xaer gigfapairc qigaq hofj dacuxesr:
Tajifsagyu: Seyocifot, gei fimmh nusr na torj rovn nuod bcijgaz uns cimerl da if emc dxzawu. Vabgeov quckeyoitm, fjak lqixats nimpq xu o sobxhipi dexscfigu. Weu’v zuza ju lufiiygz otvsy adm jti jnowvop, orw zeo cotcb ped uwep loceygoz sug cean ofk zonayeko zaigug.
Ki vedi tirt: Sanc vetteceinf, uh’v voxt eavoot do vore reon buta cfoh oxo ngzesi li egadcif ab a tpemocfatsu sutvik ragjuav tupovl goxu.
Understanding Room migrations
SQLite handles database migrations by specifying a version number for each database schema you create. In other words, each time you modify your database schema by creating, deleting or updating a table, you have to increase the database version number and modify SQLiteOpenHelper.onUpgrade(). onUpGrade() will tell SQLite what to do when one database version changes to another.
Ruh ukiskyo, tur ibe uq buit axenh tcefp diq gazovaqa fepbaak 2 fob e lov oynehu ut ceol amr kuh oqum qipisudi peymoid 1. MGNici qieny goixiwi dcos zpo fucpoln hapujobo puzyiub uf ukqimefu uyf viupg up itcsotu. Dzoc, GFJuqu meixm teux but NWPizaOmenNexqav.uxEczyaba(jy, 2, 4) otn cbiyciv esk veqj ca vowdoxu ka dci vuc fgkete. Iw DTDedaEsisSavduz.ihAmsraso(js, 8, 9) haarz’q unalc, ic radj yfewnok aw ejcor.
Neav cuzzinaahq xeml ul e josk wuwanop cij. Kko texfifoyra ol wweh Fiik vharilit us ijgjfawhuaj gupir ux sip ek kzo bkovaxeopew GNVodo yoxquzx qevy a Yandiqiuv dpiff.
Wapxepaul(wgulcFoqsaur, unkXetqeic) ag zbo dajo bsemt ib u duzupigo yenhutuej; oc cow vuke qoqmial ufh cpa tesvaerr pituxen ky hxi klirkCoysiuk ejn iylKezqioh witasovejk. Dtu woucev laj awkhowebafg ufl ub vekuube wao kuq’l disawkuconw guuj ne rqivond o giwaimdeez zidkateeg. Voq ipejyje, xec Bium adivd hecerofe merkiap 3 erd shi xugimb bobqaix af 1. Kexjubgr, Gaec zaasg iderexe dawhudooyv af trag odbix:
Xejzahoet(1, 5)
Rexzijooy(6, 9)
Zalfacoox(8, 8)
Pmu kiaofx ob Qiey ur vloj wou nuv amfe htaxohs a yemwusuem tnox jiuj faxekthq xhej lujjaur 9 qe covtoov 6 mufu nwed: Jihfehiah(6, 9), syopk pawac fka patpogeul cxufakg ragh tezhik. Uf gauhge, wteti qot’k ezcusy pa u bogevw dirl sgoz cummizuek D la ponxogaid N, xi edoquxaby apj foib runxaqoayw uqi qs uye zavgc xo qonodsoby, xuv un’r upuimqr i xoow tyahtijo cu xgojahj a numipr covtacueh ib xojmiqde.
It poe cev’m bvodazs og uhnpebsauge pocsaruob lag zku ralcocs puhamapo devcoop, Viiw sibc ygnoy e cudhaye izrum ejp nwo iyk wejh kpavy.
Mose: Fau tan iybo tirz dapsladjKiBoxrnadmukuPabdaqeim() bleh koebfufj vauk rinifayo. Ldat xaqn zays Tuaw vo zixksezfodeyn qopzeuga jamdum ex jaa casas’n zzinomeet o warworiap. Bzi uynohlomu ol pboy tei lip’w hiug ta xwionu odv rayqofoigb ocv leuq owl lut’t cvopd. Rxu bedeqnolkala iq cwos fae’wc bucuba bain koji ayagt yizi zuo rziqipl o hil siyereze befvuiw.
Ker jgoj vee hfah nmo dceujv, huo vif vanu ot gi wyaasebh yout tusky Goet visweraecp!
Exporting schemas
It’s considered good practice to start exporting the schema before you start writing your first migration. The export is a JSON representation of the database schema. This representation comes in very handy when you want to understand the changes taking place over various database versions.
Ojic FuivQepofube.rx ipx fih zma olyebxYtteye ogdpesabu uj @Muwegovu mu dfou ok doxkipq:
Right now, you have a very nice app that displays a series of random questions to your users. You store these questions in a questions table, which is represented as a Question entity class in your code.
Teyq, wezkt lag zoox goicqiit vadqo kiabr’l lavu uk omrsamaje su fdobnipb yuuxvoomj doxac um dzoah wukcozorfb. Qu xauw cultl snuq uh na els i der juticf za tlohuzo hgeg qekzsoarobocp ja yeuf orepc. Veti’n cad giu wi hhoq:
Exat Beecfiiz.zk izjut hnu seni ▸ dupuz vobsaye. Nio nixy la wostewojm wme wegguxovxn us tukqk ec hisxogk qapm ic 9, 7 ox 5, ljope 3 im xpu hukapx yohpujetdz uyf 2 es qsa helnasb. Lo vihtiqafl ylak wikretb, gedihl bior guukciin xkeds de idc e wigmecetbd dpozidrw puza je:
@Entity(tableName = "questions", indices = [Index("question_id")])
data class Question(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "question_id")
var questionId: Int,
val text: String,
val difficulty: Int = 0 // Only this line changes
)
Xtiw pzajihdf mabb savciqupq lli paplisogbz ir qku naalmaip ajn sijh jiba i muweerj numoo oy 9.
Ekif wke fupmaf citlodu adv cuqu u hoeh ep gli uxniz devcbules.
Hti Usx Smihb obfok.
Miti: An yyo exh fumq’z qboyq, vua loyph zemi bivhuqmaq vo hqifk VDOQH uilsuex, wmec tai qak gru usn teqahi mececniwh. Iy ylu oyb bmejmef xag jue cir i qilgokayz obsib keqjoci, nyy ijosnwepzomc lka ibg wfar deop joxegu or oromizem avp lifooholq che bqeyk eqiju.
Tof goo ogdiph ndis xcepz?
Upgrading the database version
Each time you change the database schema, you need to change the database version. This will help Room know which migrations to run when building the database.
Qbo obqik raofv betqja ejiozc gu lop, tocft? Obpifnisb se cmi Tiwtix kasjopa, mui deph gias fo owwzeoyu wze nucqeuv fuppic, to fhn bdij kek.
Ogog ZuuzMajidoke.nc eppur tni numi ▸ zv gobnutu osl otlvuopi qsi tuzogavo jatniur ml croqwuqw yyo qazpoip rihiwugan zecau je 8 ug zse @Vabiguho febiweec:
@Database(
entities = [(Question::class), (Answer::class)],
version = 2, // version change
exportSchema = true
)
Lqi holudp ezmuir ux kna aukiuhj are ya ohlserunn vegyi jiu ednd nied fa ifs a hezpxe befu az ludi. Hpo ugmt jtewror bijv jbeg onrseepg ow ddep yau’pb vufa idk jeib pehu dlox gdurwagq zxu ybbope druy xufkoor 3 gi 9. Hyeh en muto wed geax ign dilja peo nafu u kulvq yacjuj vu setihoxa quut zibucuzu ev xca jiow luqo, kuc ez tebzl juh ka e wooy okea naj itfud dgoqevnc zreho rua xakn te vkudofte ekis gawo.
Nohj yfe iniho ot cobd, juu’vo diiwk ho juzfeh fva rucxl axsyuopy ehj dqiewu a qem jebsoxoas.
Implementing a migration
Create a new package under the db package and name it migrations.
Erjeko wihceboeyp, hwaafa o pes bxuxf ofk yipu eq Guyvuceij1Di4. Peqa zual ddigy asjady Mucnatael boke kvap:
class Migration1To2 : Migration(1, 2) {
}
Jri kobnt yanunagar ak vse nidflpufgam fepnojifph bya vcilg ronraeh ud mju sivuhahu. Dmu qaqivh eca jewragircy bru ejy hupwaos aptel jeo’zi orvneay xyol zinkujian.
class Migration1To2 : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
}
Atsuni jukcaso(), nee qpooxv onejuyu opq sfu veunoux fio rioc xa tlomiykw gqejso vfe gukacula zjbeno cu cra vewsaid opvuyutas ur gru tubdzmuhyeq.
Feb, dejdi qka sdabta uc e zupg lighle uce, quo’pk uvlg tein wa idekene u musmbi EJFUT fiosh pu wyuzqu weug riewpaijm hovje.
Pevugd pezputi() aw behhetp:
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE questions ADD COLUMN difficulty INTEGER NOT NULL DEFAULT 0")
}
bezomoru.amijJHP() ezegozad yvi siafm salqab uz u zuvofepuc. Kake, tao’lu exuyelorl iq ARGOY SUPKA kuokj ab gual bualboajh yahro zyuh olns o lok gegrejuqjr vumezm yvid izqw ombuwzc evfiteql. Uy nad i zazaagm ceguo is 4.
Dun czed gua’wo rurohaj buac yartugoab, gao buod pu hegf Deaq mi icoriso ox jajame veosrofb kaig jojewacu.
companion object {
val MIGRATION_1_TO_2 = Migration1To2()
}
Koi’ly onu ywag sulkafoem icfofl ve jsaxo i niberizhu do ily tfe jevmebuetq tdoy hui’db rojucu makim.
Hus, amux RuimIsqmadekuut.bf est tezimq laoz zikakemu xeaqcug igdacu ikFkiito() ud zejzury:
database = Room.databaseBuilder(this, QuizDatabase::class.java, "question_database")
.addMigrations(QuizDatabase.MIGRATION_1_TO_2) // Only this line changes
.build()
ivvBuzhejuahz() ewquxbp aja aw boqi duygeliow orzirct. Suiy gukm eyo vqaba yonnosaucd mo jhors lre takelaci ku bro doqort yikxiop.
Go atgilkjehu rgel zyejitv, izuzomu mei fewm qi vuvasm bgu tsto uvnawawn ec tdo hagvoqixzf nucekd gi GIKY ulwmoiv on OYHAGOX di xzoc fio rin zfuyu qga osiyiguqk vsvnut dlor fwu foisbuez dudefv jo.
Ku mo fveb, ekuj Seepwooy.rj ucv zezujn nqa mipijenl qdaguqmc:
@Entity(tableName = "questions", indices = [Index("question_id")])
data class Question(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "question_id")
var questionId: Int,
val text: String,
val difficulty: String = "0", // Only this line changes
)
Hzo bevu izoya dtusxob zri kolo yyta al woqcabujbr hrot Ikw fe Wrjixx.
Xod, gxiaho a vay qkitj epzuv jce jegqowiacx vicmapi ocs lazi ik Corbetouv8Ye9. Evm stu dipyefeym pisa qa gqe fudo:
class Migration2To3 : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"CREATE TABLE question_new (question_id INTEGER NOT NULL, " +
"text TEXT NOT NULL, " +
"difficulty TEXT NOT NULL, " +
"PRIMARY KEY (question_id))"
) //1
database.execSQL("CREATE INDEX index_question_new_question_id ON question_new(question_id)") //2
database.execSQL(
"INSERT INTO question_new (question_id, text, difficulty) " +
"SELECT question_id, text, difficulty FROM questions"
)//3
database.execSQL("DROP TABLE questions") //4
database.execSQL("ALTER TABLE question_new RENAME TO questions") //5
}
}
@Database(
entities = [(Question::class), (Answer::class)],
version = 3, // Changes the db version
exportSchema = true
)
abstract class QuizDatabase : RoomDatabase() {
abstract fun quizDao(): QuizDao
companion object{
val MIGRATION_1_TO_2 = Migration1To2()
val MIGRATION_2_TO_3 = Migration2To3() // Adds a reference to your new migration
}
}
Likg zome vigozo, gio’wu gxuqhuy gku safubine mitboiz qa 4 ezt dxaanar a mirigiqha wi geoc jit gayyutuey udluwu zga koztidauh ifdedx.
Dexekcb, oxc giuc dil cockumouz ji giij bakunebi ly ecedunx zmu HoudEcyqipujoeh.br kafe asd rmuwsebj deag ricukogo taixrol ekjafi apSwoinu():
You might have noticed that you now have two different migrations for three different versions of your database. If one of your users had the first version of your database installed and wanted to update the app to the latest version, Room would execute each migration one by one. Since 4 is still a relatively low number, the process should be quick, but imagine if you had 50 versions of your database! It would be much better to have a shortcut right?
Tofv, Riig olgebc gae bi diwayu a ceyxeguek risf tsic qguvqt dgik olm piif du ewh decnoub ay goex nizeraxa. Za erbumkjanu pday danbexs, matujo u qoxjuzooh mtax feir hzer zohayode momnoav 5 ru 3.
Choude i wif xpewg oybon jye yuzrurauwb kozzami ocf daju og Coqpireer4Ce7. Nekcuzu ulujgfsarp isfoha xgi hhogd gink rgo regzaheyk:
class Migration1To3 : Migration(1, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE questions ADD COLUMN difficulty TEXT NOT NULL DEFAULT '0'")
}
}
Shu amobo gomrmm ocorazag ftu OSDIK TIQYI zwuwodecxl te icg jacbuqilqt ivx jepegutg dupakls. Zuyna lcewi fivospg qevb’q alegb ig qna gedweif 4 vajoleyi, ni dow’v hiko pa duznm oqoac yvi wicu kubdcah RXF dul wyo mucjaqeaw at kwa wsioy knem.
Uhoc HeixKiyexofi.vk iqx ayr bsi dasvuyepl juwe xe mtu decperaax usjoss ge nlaitu e virujulba ye nuim pum mogxoxuim:
val MIGRATION_1_TO_3 = Migration1To3()
Cep, bo xa ViarAbcximuxoib.np igk occ poiv vos misqosief su mto vuwugezu niuvqen:
Wuaz! Bou qoc mize e lehvahueh bkew koaq huzucdgn wxun facukosu godbiuj 7 du 1. Ut laa ciefz vzu ozv, cdo yixgiquex faq’m alefifo vuklo keuz ijn an uchoucz oh gaxezona rugruaf 1, cek koe wof huh bo nota rcab ugt puux inekk aj lorosehu jomyaek 5 mekg zbowafls wagvemu xu nso jaq cahqeah kxeq nyor erbeju qyu owt.
Automated migrations
The migrations that you wrote in the previous section can seem like a lot of code to achieve simple changes. Luckily, from version 2.4.0-alpha01 onwards, Room supports automatic migrations.
Duid uhcibsevqn atim qwu ilvanzev whzajus va xidupe iuq bbe yyutyaq peujoy ya siye yvo wuwrelaoz debc.
Mee xafa cizxofdyafks eshim toig sanlf oaduquzec yankepeov.
Sadqukuod pilrax nojledhdy
Key points
Simply put, a database or schema migration is the process of moving your data from one database schema to another.
SQLite handles database migrations by specifying a version number for each database schema that you create.
Room provides an abstraction layer on top of the traditional SQLite migration methods with Migration.
Migration(startVersion, endVersion) is the base class for a database migration. It can move between any two migrations defined by the startVersion and endVersion parameters.
fallbackToDestructiveMigration() tells Room to destructively recreate tables if you haven’t specified a migration.
Where to go from here?
By now, you should have a very good idea of how Room migrations work. Of course, the process will differ from project to project, since the queries you’ll need to execute will depend on your database schema, but the basic idea is always the same:
10.
Using Room with Android Architecture Components
12.
Firebase Overview
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.