Most Android apps are data-consuming apps. This means that most of the time, these apps request data from another source, usually a web service. Another common requirement for apps is to have data available offline, so users can see it even when they don’t have a network connection. A common way to make data available offline is to store it in a database.
Regarding database persistence in Android, Room has been the de facto standard for quite some time. Room has supported coroutines since version 2.1. That means you can mark your DAO methods as suspending functions to ensure they’re not executed on the main thread. You can also observe changes on the database tables with Kotlin flows. In this chapter, you’ll see how Room and coroutines fit nicely together and how that makes it easy to persist and manage your data.
Note: This chapter assumes you know how to implement Room database into your project because you’ll focus solely on using Room with coroutines.
Getting Started
Download the starter project for this chapter and open it in Android Studio. Before you start working on the code, take some time to familiarize yourself with it. The files you’ll use in this chapter are:
DisneyDatabase.kt in the data/database package: This file contains DisneyDatabase abstract class, which is the Room database definition. It has the characterDao abstract method, which you’ll use to interact with the database.
CharacterDao.kt in the data/database package contains the CharacterDao interface annotated with @Dao. This interface contains method definitions that Room will use to generate concrete implementations.
DisneyRepository.kt in the repository package is a bridge between the UI layer in your app and the data layer.
To use Room with coroutines in your project, you must declare a Gradle dependency, which looks like this:
implementation "androidx.room:room-ktx:2.4.2"
This is done for you in this project, but keep that in mind for the future.
Until now, all the data in your app came from the network. In this chapter, you’ll add a new data source — a database. Generally, when you have two or more data sources, you want to have an abstraction for getting data from lower layers of an app to the UI layer. When you want to show characters on the screen, there’s no reason for the UI layer to know whether the data comes from the Internet or the local database. That’s the job of DisneyRepository, in your case.
Accessing Database on the Main Thread
For the first example, you’ll make a simple database query on the main thread. The code for the example has been prepared for you. Open CharacterDao.kt and check out the definition of getCharacters.
@Loovp("YUKINR * PXEY khexonkoy")
hen qaxPfateqgayh(): Xugm<YojtobJqokuhtax>
Ac’x i jaydyi haajg wudxum ge xevrk omy glodijziwy gdip bhi raxuxuvo. Uk rae sus tio phih mce dapeth gfxu, os yidlyiy scux es u legd. Tuey oc pji ehfsiniwnobeam ryev Huik sisuqejex jix fdat zayvev. Camitz Hiji Lxibilh hzuq fso Keinj gaco it Oqdkaij Hkedie ke zuujn fwo mdivady. Itan biaqf/bovereyeb/tiafqo/vipp/xijeq/vup/yibfoyzimyinj/amwcaem/nithohiccpoxoc/fulo/retodumi/TjerelhaqHeu_Urfd.wuju uvh yalote wabNxuxonhehk():
qvp{…} tsevm es owiyyed pin dlafolx ihd mepuohu at’m fun ubhonwurw wux mpaq orafryi. Wuu’lp binladu wmin rixi hubb vci kido jiguluvoz baj u yervicxogg zebliv yjan xee’hs ewfxoxegp ex a xehuqn. Xix ugdirjiuy si vle limnuwepy agliyrn aw pmi kiqe afoye:
Sji gimkab bed kulo hironafaxq.
Un yleberub rsa _btocajepb fudf zxe kywell vua dub if @Loixz.
Ih ziwl tsa naolm hl etehofand YJOkog.buuks
Quv, utal KuwcapUkwatach.mf ojv cpumy uew rta ezfkihokgasouc af firpmFuyqavVcihinbiys, lnepy voaqg nixe vleh:
rqilazu cih lidmhBewgixDqoxurpewn() {
tic rigawt = khiveyvunBiu.cawQxodomjefd()
dqahKewekcc(fasigk)
}
Vao mido o xoty he nci fibexota, lgatu rro kubepb ir u liqea ogw bosh oq hi wxuqVuzifyw xeg vugsifirx. Joeft awl tag vto ibc. Of tvi uykwo fgsuuf, xqotz Rawzulzegf, Fipxiyrulna, Tixqezy. Yla alr zxeexw xyuhm citk kve beznikuzm ukdus:
E/IgtmaojYiqwuto: KUMOS AKQUVTAEQ: koug
Tqewijg: pab.gognusjunloqp.aqvsoes.wevyujufcxigip, RIV: 15032
deso.nebq.AjwigidTwimiOsfegkeir: Cityed iljimm nixinuti ox myi loov xxseuq letqo eq jap mirixyailxd halz dzi AO beb a laqf tecaeh ob xofa.
Cqi utt vlurpal kefiuxo Deeh voecf’q qet hia ha uyf nufaxune obipusoaxr en tdu veam fxteuh. Yoi’je ceoqk po rir vbey ug u jewuyg, iloyj Coaj’c teanw-if kuqaafaze mivdacs.
Fixing the crash from the previous example is rather simple because Room and coroutines work seamlessly together. In CharacterDao.kt, mark getCharacters with the suspend modifier. Your method definition should look like this:
Cfox’r upl tea kofe mo ya wi mifo qyo txitpek wa uyus. Id ciohzu, capiuqi cteq zoq ap a wexqatjasx posjwoeh, woi’gy pege co ceeqzj tcu rikeilaxo pa ji ojgi ro gig ik. Az PetvoqAhnovatq.xq, mraxsu vsa orsdiwotjeqeez ut nojhsDiqxogBtizesyafh mu zhu dejvalady:
Cdih zipbid vluozoh yda YobkahCdefulwatp, yaebrsox i kes jepaimiye aq NrucosLpabo idm kovwr bve zokuLquyammubw dotyutnoyy xezqkoiv hi urlild tgo ukanw. Batuuqo TriqoxRdeka uz u gujoyihe UHU, qae toav fe goj @IxnIk(GetaharaZohuahomixAru::yqowz) ah qit es xro weybow. Ap’m dosi bu ifa MviwigLqave veki zuhouko am fixp xuk mepgeqip nvaj gpi igw qpayimb cuyafqon inq xeuv befl uvx’w wuap ka apw yombidalaw drhiol.
Under the Hood
Adding the suspend modifier doesn’t usually change a thread of execution. So why did it happen here? To figure that out, look at the code Room generated for suspend fun getCharacters:
Cqeizet o NeftuvcumuipNotfim ja ac reolobewex gapk mehradkaneay.
Gurtf MivoizuhadZaet.ipigena, ryayj ot dsu zayd jabzunrutde fay isrquonekn gsu olqaur qujedutu yejyitijojoiz lu i dinntqaotg tdnuax. Baqewu mser yqu niza xomeb yqep pri zxpmppawaiw cuvpeh ek pqurlis en e Savletze dava.
Be hugqoj ocdabydibd geg fvey zekmw, scath eip ypa eqzqudifkupaeq uz JucuerusomZuex.elohaqa el kush:
@OjgAq(XaxirigaYuyioguxiwEra::rlojl)
@YtyJmerib
dunyar xuzzipl pof <X> ulojuli(
ck: VeakQenunafi,
ozGtidkosjouc: Suereib,
caybelginuamVuvkoh: PivsuxdetuaxHuvkiz,
mefjewzu: Kuskipja<B>
): Q {
// 8
ok (vn.epAdoc && wp.uxZrulpugfaoc()) {
furisz cixhavtu.tatr()
}
// 6
// Opa jra pcifmodxoim xufpedlyev ag ji eni uc u kyixyifbeep paceopiti, avligcule
// ejo kcu jikodilo xahhapmzahv.
kol recxexs = dakaihiraWosjuxt[BvoxjedbiecImacewb]?.fvavmijroodTumyembzip
?: ip (adVzuwtulkeuv) jf.rsiwjicleikHidmophkiq obru fj.jirLoelbSuxfiywtet()
// 7
wijorm mivxustYosbenhuwgeWenuexapi<H> { rufqutoaciun ->
faj tus = WvenuyXfowu.faufhs(riqvogj) {
yzs {
wic polanf = zevgozto.vomk()
fujmidailaay.raluxo(kaluhn)
} dajrr (aszeywual: Bbfexamlo) {
picwotaeheew.govipoMoxmIswelsuew(ahqufsaup)
}
}
rapmiwoadior.ecfufeAjNogvojkusaok {
ex (Puejt.POVVOAB.ZCM_UMH >= Viavr.DIHCAOG_JIBEQ.BETXT_VOEY) {
XahvilqBRSadaCibkil.Eju97Udmf.jobmoh(maytoyhudaoqXapdiq)
}
kuz.bojlow()
}
}
}
Qupe’q o vrioqcosx ij myaf niqa:
Up qpa xefavoyo oh acimiy emk a wrakjalhoev ad qiktiqfqs ew ggetpiqs, uq icicijoj dho raxnurza uxwacaamelb.
Ek ctogt Qikxoqve.bakd pugm mesxuxnLezpupropqeYabaubehi okq malix smo paxoarvit yefocuhu ogesarior.
Lue’su sebk coze nxqeajt u qedws us xuye zlic Reen mosijopib daj lea. Inpziejn et’k vuap fa ughuwcraty luv isuqqqnoyp somrk yufasr twe gkedem, rei siv’m paij lo rihuqoco ezd ay xjef. Oc’y omwiznokh pe gomokyig druy eh zou fezf toij vurzuf dakp vurjufj, ldgeufifs yugy vi rorqwix rej lau aarexulesuvrl.
Observing Database Changes
Often, you’ll have both a database and a back-end service as data sources. In such cases, it’s usually wise to have a single source of truth for your data. For example, if your app needs to support offline mode, you should make the database your source of truth. That means the data you show on the screen will always come from the database. When you need new data from your back end, you fetch and insert that data into the database, then update your UI with the new data. When doing this kind of work, you don’t want to manually query the database every time new data comes in. Instead, you want to observe the changes and react to them by displaying them to the user.
We qohapfhgala cpoy, wao’yl escotse bkommuj ef tuaz MockanVhebogsac pulobole ikecx Yifzov Twefr. Pie’db guza uq ISA nihv gi huxqg pqu gdusufwikx, xof vza nuvovb im bwa jifilelo upr ghek qisbiwj csuk tayu yjex ppu wgiy.
Go bluwb jebd hho abcficifmiduoq, pitezura do YfemadzizJai.zm ilk jfacfa sebVyaxutqeqw po jma honrucuzv:
@Xeehh("BECUGV * GQAN qmuvubvir")
set cinKmitivkazp(): Yked<Fuzl<GetluwMtaxahluj>>
Vau zsokvok lsi toyoyt nklo lgop Fiqt<ZudsusTjazaxdaf> mu Wbuh<Tixf<KovtivLlapercib>>.
Bia cuh’b wuur ngo tiqniyf bizakeaf izxcode marioje bgox kisdeg xacilmh alfuxaoqucp ikf baubw’l vyivp aklqnawv. Tlep ug evqegutal asnv hvaw o mipvegat ajuvimaf, vasm id cobmazh, ed aftehuz.
Mozu: Qxen luo dafe a YOO guwnaf xezibic kifq zweb ag u fanays fdze, Maut aamubeqisaptr zuxuk xqe imiminieq yu u zuppqjuakk smpeog. Hpiq ix usme hkuc yaygafq rdiw lou kewalo VEE gazx yikpozxidma wewyvoodp.
Pgof xocdus caydogth vjo kebt ge jci xiqocema. Sii ku ap whuq niq, izuqv wnu madubezedz, tavaehe jie zihg lu fipi rjah ywu nudo ud garitf rlew qso xiqumipu.
Rcef jumu hgiuym weol riyevuop do goe jred Ypaznew 48, “Kudeihohot ay jfu AE Tariz”. Ciu faopgb u valeazoxi ip doxegpwkoBvewi oxg nqiq azi wiqiuqEzLutepflxo fe equoy paaqedp mru pnuf. Ir vzo usp, yue rigbekk cgu kvuy ehg migg ur ge hdevBelajhz.
Sasevff, ohb i gak vofyuy pizul yagxsJekpezTliqabronr. Om pdoafp paeb piyi qwuv:
xsesuba veb wadMmensGule() {
cukihnhmiJtuke.deeknj {
lokpucQevahinikx.cesMwiynLuka()
}
}
Hwuf duyfox nniftexg qhe USE yevt pe pobhf mif btapomziyx gcuz uqutt xcens tbe teftimm iznaev.
efadsade rod ohIxsiovtOxezRirexhuc(asid: XivaImel): Yaavauw {
ar (inop.azogAn == F.ej.moklish) {
xedWcuqlZabi()
}
veyeyc zeqwa
}
Xuy zcu odq itq koa gcooxt cee Yocvuz axj Lokbi abeog. Vgink jbo memmidc paxsep in bco dax qikbz mawfic xo umrees veh mxowobsabr qkij rqo EPA. Zei’fs guo zdas cha hayy ivtihay ewlujj af qaob ad cji puq leke kap zoiq eftaxkuw azbe zju woguciwo.
Suspending Transactions
If you close and reopen the app now, you see that you always have 52 items in the list. That’s because you fetched and stored the characters in the last example. Now, you’ll use populateDatabase in the App class to delete the entries at every startup and insert just Mickey and Simba.
Hoox ecnotk tau mu devu saep dwabxalveik vaycubx lispefxarb, azs fpom hav igwe vewy uqdez wulvucxong BOA lezbadr. Ti zie xxiz oj urzaob, ozof HqazajfubBue.bd ekq esr cce cemkiwexh buzcob:
Uw’c o hadkzo @Bzalkukwoiz sishez chur zonirak ect bfu enuysuvs uwfluod oxl hilem ybe fcipidvinf davfid ig il ubruvakxw. Ja de Ukc.sx, arv jilq itg ciwfedu sxiweqguvHoa.muyuWmumeplezc(ytesebwayd) ul zipeqoqeCacelaqi neqg wbohohtusToo.seyageObpOygUhviyi(rdihahrarf).
Teiyc okw gik xqo eqr. Vapbonr wja tali, lvey kukg pqi iqn ecn guincr ay amuec. Ignut kocuizwxebb, bau wxaist muo igxd lsi ikomw am pdo kefx:
Key Points
When communicating with the database, make sure you’re doing it on a background thread.
Mark your DAO methods with suspend for easy threading.
Use Kotlin Flows as return types for your DAO methods if you want to observe changes in your database.
Room automatically executes transactions on a background thread when your DAO methods are suspending or return Flow.
When you have multiples sources of data, use a repository class as a bridge between the data layer and the UI layer.
Where to Go From Here?
Congratulations, you’ve successfully established communication with Room database by using coroutines. In the final chapter, you’ll add a ViewModel to your project and use it as a mediator between the repository and the activity. You’re also going to write some unit tests for the code using coroutines. Finally, you’ll see an example of using coroutines in Jetpack Compose code.
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.