Room je jednou z technologií pro perzistenci dat v Androidu, které používám, když aplikace potřebuje strukturovaná lokální data, relační dotazy a udržovatelnou databázovou vrstvu nad SQLite.
Room používám především v Android aplikacích založených na Kotlinu, kde musí data přetrvat restart aplikace, zůstat dostupná offline a čistě se integrovat s moderní architekturou postavenou na coroutines, Flow a Jetpack Compose.
Room pro mě není pouze pohodlná nadstavba nad SQLite.
Je to perzistentní vrstva, která aplikaci poskytuje jasně definovaný kontrakt mezi kódem v Kotlinu a podkladovou relační databází.
Jak používám Room
Room používám pro úlohy, jako jsou:
- strukturovaná lokální aplikační data,
- offline-first úložiště,
- záznamy vytvořené uživatelem,
- historie a logy,
- data uložená do cache z externích zdrojů,
- relační datové modely,
- vyhledávání a filtrování,
- persistentní stav aplikace,
- synchronizace na pozadí,
- větší datové sady, které nepatří do DataStore.
Room je zvlášť užitečný v situacích, kdy mají data skutečnou strukturu a je potřeba nad nimi provádět dotazy, nikoli je pouze ukládat jako několik hodnot preferencí.
Room nad SQLite
Room používá jako základ SQLite.
Rozdíl spočívá v tom, že nad ní poskytuje výrazně silnější vrstvu pro aplikaci.
Namísto ruční správy raw cursorů a opakujícího se databázového boilerplate napříč aplikací mi Room umožňuje definovat:
- entity,
- DAO,
- databázové třídy,
- relace,
- migrace.
Díky tomu zůstává perzistentní vrstva strukturovaná a snáze udržovatelná.
Stále však zohledňuji chování samotného SQL a SQLite, protože návrh dotazů, indexy a struktura schématu mají přímý vliv na výkon.
Entity
Entity reprezentují databázové tabulky.
Strukturu persistentních záznamů popisuji pomocí Kotlin data classes.
Aplikace může například obsahovat entity reprezentující:
- uživatele,
- relace,
- záznamy,
- kategorie,
- historii,
- data uložená do cache.
Každá entita definuje pole, která k danému typu dat patří, a způsob, jakým se mapují na databázové sloupce.
Perzistentní model je tak přímo viditelný v kódu aplikace.
Primární klíče
Každá entita, která potřebuje stabilní identitu, by měla mít jasně definovaný primární klíč.
V závislosti na aplikaci mohu používat:
- automaticky generované číselné identifikátory,
- UUID,
- identifikátory dodané externím systémem.
Správná volba závisí na tom, jak jsou záznamy vytvářeny a synchronizovány.
Například u dat z API uložených do cache může externí identifikátor již přirozeně fungovat jako primární klíč.
Relace
Room je užitečný v případech, kdy aplikační data obsahují vzájemné vztahy.
Může jít například o:
- jednoho uživatele s více záznamy,
- jednu kategorii s více položkami,
- relace mnoho ku mnoha,
- struktury rodič–potomek.
Tyto vztahy modeluji záměrně, namísto zbytečného duplikování dat.
Relační struktura se obvykle stává snáze udržitelnou ve chvíli, kdy jsou stejné informace potřeba ve více částech aplikace.
Data Access Objects
DAO definují způsob, jakým aplikace přistupuje k databázi.
Používám je k oddělení SQL operací od UI vrstvy.
DAO může zpřístupňovat operace, jako jsou:
- insert,
- update,
- delete,
- query,
- observe.
Koncepčně:
UI
→ ViewModel
→ Repository
→ DAO
→ Room / SQLite
Toto oddělení drží detaily perzistence mimo composables a další prezentační kód.
Ověřování dotazů při kompilaci
Jednou z nejsilnějších výhod Room je možnost kontrolovat SQL dotazy už při kompilaci.
Pokud dotaz odkazuje na neexistující tabulku nebo sloupec, problém lze odhalit ještě předtím, než se aplikace dostane do produkce.
Tím se výrazně snižuje riziko nasazení jednoduchých chyb v databázových dotazech.
Zároveň je bezpečnější refaktoring SQL, protože změny schématu mohou na ovlivněné dotazy upozornit už během vývoje.
SQL v Room
Při práci s Room stále píšu SQL a rozumím mu.
Dotazy mohou zahrnovat:
- filtrování,
- řazení,
- joiny,
- agregace,
- vyhledávání,
- stránkování.
Room nenahrazuje znalost SQL.
Poskytuje typovanou Android integraci nad SQL.
Právě to je jeden z důvodů, proč jej preferuji před přístupem, kdy je databáze vnímána jako neprůhledná perzistentní služba.
Kotlin Coroutines
Room se přirozeně integruje s Kotlin coroutines.
Pro operace, které mají vrátit jeden výsledek nebo provést jednorázovou změnu v databázi, používám tam, kde je to vhodné, suspend funkce.
Databázová práce tak zůstává mimo hlavní vykonávací cestu uživatelského rozhraní a přirozeně zapadá do moderní architektury Android aplikací.
Přístup k databázi by neměl způsobovat zamrzání rozhraní.
Flow
Room může zpřístupňovat sledovatelné databázové dotazy jako Kotlin Flow.
To je zvlášť užitečné v reaktivních aplikacích.
Koncepčně:
Room databáze
→ Flow
→ Repository
→ ViewModel
→ Jetpack Compose
Když se obsah databáze změní, nové hodnoty se mohou automaticky propagovat aplikací.
UI se nemusí opakovaně dotazovat, zda se něco změnilo.
Room s Jetpack Compose
Room funguje obzvlášť dobře s Jetpack Compose.
Obrazovka může sledovat stav zpřístupněný ViewModelem, zatímco ViewModel získává reaktivní data z repozitáře a Room.
Například:
Room
→ Flow<List<Item>>
→ ViewModel
→ UI State
→ Compose
Když je položka vložena, aktualizována nebo odstraněna, odpovídající část rozhraní se může automaticky aktualizovat.
Vzniká tak čistý model uživatelského rozhraní řízený daty.
Repository pattern
Mezi Room a ViewModel obvykle vkládám repozitář.
Repozitář může kombinovat:
- data z lokální databáze,
- data ze vzdáleného API,
- business pravidla,
- chování cache.
Room díky tomu není přímo provázán s každou částí aplikace.
Zároveň jsou jednodušší budoucí změny.
Repozitář může například později přidat synchronizaci, aniž by každá UI komponenta musela rozumět nové architektuře.
Offline-first aplikace
Room je zvlášť vhodný pro offline-first návrh.
Aplikace může používat lokální databázi jako svůj okamžitý zdroj dat i v případě, že není dostupné síťové připojení.
Typická architektura může vypadat například takto:
vzdálené API
→ synchronizační vrstva
→ Room
→ Flow
→ UI
Uživatelské rozhraní čte data z lokální databáze.
Síťová synchronizace databázi aktualizuje ve chvíli, kdy je připojení k dispozici.
Aplikace je díky tomu odolnější vůči nespolehlivému mobilnímu připojení.
Ukládání vzdálených dat do cache
Room je vhodný také jako strukturovaná cache pro data z API.
Namísto uchovávání stažených informací pouze v paměti je může aplikace persistentně uložit lokálně.
To může přinést:
- rychlejší spuštění,
- offline přístup,
- nižší počet síťových požadavků,
- kontinuitu mezi jednotlivými spuštěními aplikace.
Strategie cache však stále musí definovat:
- jak dlouho zůstávají data platná,
- kdy dochází k obnovení,
- co se stane při selhání sítě.
Samotná databáze politiku cache neurčuje.
Single Source of Truth
V mnoha aplikacích preferuji, aby Room fungoval jako lokální single source of truth.
Namísto toho, aby UI samostatně sledovalo síťové odpovědi a databázová data, mohou být vzdálené aktualizace zapisovány do Room a rozhraní může konzistentně sledovat databázi.
Tím se snižuje počet vzájemně soupeřících zdrojů stavu.
Architektura se stává snáze pochopitelnou, protože UI dostává data z jednoho předvídatelného toku.
Operace insert a update
Databázové zápisy definuji prostřednictvím metod DAO.
V závislosti na konkrétním použití řeším:
- nové záznamy,
- nahrazení,
- částečné aktualizace,
- chování při konfliktech.
Řešení konfliktů by mělo být explicitní.
Tiché nahrazení existujícího záznamu může být správné pro cache, ale nebezpečné pro data vytvořená uživatelem.
Strategie závisí na tom, co daná entita reprezentuje.
Transakce
Pro operace zahrnující několik souvisejících databázových změn používám transakce.
Například:
- aktualizovat nadřazený záznam,
- nahradit související podřízené záznamy,
- aktualizovat metadata,
- potvrdit operaci.
Pokud jedna část selže, lze celou transakci vrátit zpět.
Tím se zabrání částečně aktualizovanému stavu aplikace.
Indexy
Indexy mohou výrazně zvýšit výkon u polí často používaných pro:
- filtrování,
- joiny,
- řazení,
- vyhledávání podle hodnoty.
Indexy přidávám podle skutečných vzorů dotazování, nikoli automaticky ke každé vlastnosti.
Indexy zrychlují čtení, ale zvyšují nároky na úložiště a zápis.
Správná rovnováha závisí na způsobu, jakým aplikace data používá.
Výkon dotazů
Lokální automaticky neznamená rychlé.
Neefektivní dotaz může způsobit viditelné výkonnostní problémy i na mobilním zařízení.
Sleduji zejména:
- velké výsledkové sady,
- zbytečné joiny,
- chybějící indexy,
- opakované přístupy k databázi,
- nákladné řazení,
- zbytečné vytváření objektů.
U větších datových sad navrhuji dotazy tak, aby načítaly pouze informace potřebné pro aktuální obrazovku nebo operaci.
Stránkování
U potenciálně rozsáhlých kolekcí nemusí být nutné načítat všechny řádky databáze najednou.
Room lze integrovat s paging architekturou tak, aby aplikace načítala data postupně.
To je užitečné například pro:
- historie,
- dlouhé seznamy,
- katalogy,
- rozsáhlé datové sady uložené do cache.
Postupné načítání snižuje spotřebu paměti a může zlepšit odezvu při prvním zobrazení obrazovky.
Migrace
Databázová schémata se vyvíjejí společně s aplikacemi.
Pokud aktualizace aplikace změní schéma, existující uživatelská data musí zůstat platná.
Migrace Room považuji za důležitou součást release engineeringu.
Migrace může například potřebovat:
- přidat sloupec,
- vytvořit tabulku,
- změnit relace,
- transformovat existující data,
- vytvořit index.
Cílem je bezpečně převést existující databázi uživatele z jedné verze na další.
Automatické a ruční migrace
Room podporuje automatické i ručně definované migrační cesty.
Jednoduché změny schématu mohou být vhodné pro automatické migrace.
Složitější změny mohou vyžadovat explicitní SQL a transformační logiku.
Přístup volím podle skutečné změny schématu, namísto snahy vměstnat každou migraci do jediné metody.
Testování migrací
Migrační kód by měl být testován.
Migrace může fungovat dokonale při čisté instalaci a zároveň poškodit databázi každého existujícího uživatele, který aktualizuje ze starší verze.
Proto považuji aktualizační cesty za součást požadavků na kompatibilitu aplikace.
Historie schématu a migrační testy pomáhají ověřit, že starší databáze mohou bezpečně přejít na aktuální verzi.
Historie schématu
Room může exportovat informace o schématu reprezentující jednotlivé verze databáze.
Tam, kde je to vhodné, uchovávám historii schématu pod verzovací kontrolou.
Poskytuje užitečný záznam o tom, jak se databáze vyvíjela, a podporuje testování migrací.
Databázová architektura by měla mít historii stejně jako zdrojový kód aplikace.
DataStore vs. Room
Room a DataStore používám k různým účelům.
DataStore je vhodný pro:
- nastavení,
- preference,
- malý persistentní stav.
Room je vhodný pro:
- kolekce záznamů,
- relační data,
- prohledávatelnou historii,
- složité dotazy,
- větší strukturované datové sady.
Například:
preferred_theme
→ DataStore
download_history
→ Room
Volba správné perzistentní vrstvy udržuje oba systémy jednodušší.
Room vs. přímé SQLite
Pro běžný vývoj Android aplikací preferuji Room před přímým používáním SQLite API, protože poskytuje:
- typované entity,
- DAO rozhraní,
- ověřování dotazů při kompilaci,
- podporu migrací,
- integraci s coroutines,
- integraci s Flow.
Přímý přístup k SQLite stále existuje v podkladové vrstvě, ale většině aplikací prospívá dodatečná struktura, kterou Room poskytuje.
Room a REST API
Room často spolupracuje se vzdálenými API.
Aplikace může:
- vyžádat data z REST API,
- ověřit a transformovat odpověď,
- uložit ji do Room,
- zpřístupnit ji UI prostřednictvím Flow.
Vzniká tak čisté oddělení mezi síťovým přenosem a lokálním stavem aplikace.
Room a JSON
Vzdálená data běžně přicházejí ve formátu JSON.
Tam, kde se jejich odpovědnosti liší, držím síťové DTO a databázové entity koncepčně oddělené.
Struktura odpovědi API nemusí být nutně vhodným dlouhodobým databázovým schématem.
Mapování mezi oběma vrstvami umožňuje, aby se každá reprezentace vyvíjela podle vlastních požadavků.
Type Converters
Relační databáze podporují definovanou sadu datových typů sloupců.
Aplikační modely mohou obsahovat bohatší typy Kotlinu.
Tam, kde je to vhodné, používám Room type converters pro mapování mezi nimi.
Může jít například o:
- enumy,
- reprezentace data a času,
- malé value objects.
Konvertory používám opatrně a neskrývám rozsáhlé složité struktury do jednoho sloupce v případech, kdy by měly být skutečně reprezentovány samostatnými relačními daty.
Databázová práce s vlákny
Databázová práce by neměla blokovat hlavní UI vlákno.
Moderní architektura Room se integruje s coroutines a reaktivními dotazy, takže perzistence může probíhat asynchronně.
To je zvlášť důležité na Androidu, kde může blokování UI vlákna rychle vést ke špatné odezvě nebo ANR.
Synchronizace na pozadí
Room může fungovat také jako lokální perzistentní vrstva pro synchronizaci na pozadí.
Například:
worker na pozadí
→ vzdálené API
→ Room
→ UI sleduje změny
Uživatelské rozhraní nemusí vědět, zda aktualizace přišla z akce v popředí, nebo ze synchronizace na pozadí.
Jednoduše sleduje výsledný stav aplikace.
Integrace s WorkManagerem
Pro spolehlivou odloženou synchronizaci lze Room kombinovat s WorkManagerem.
To může podporovat úlohy, jako jsou:
- periodické obnovování dat,
- fronty pro nahrávání,
- opakování neúspěšné synchronizace,
- zpracování lokálních záznamů po obnovení připojení.
Room ukládá stav.
WorkManager řídí, kdy se má práce na pozadí provést.
Zpracování chyb
Databázové operace mohou selhat.
Mezi možné příčiny patří:
- poškozená data,
- neúspěšné migrace,
- problémy s úložištěm,
- porušení omezení,
- neočekávaný stav aplikace.
Perzistentní kód navrhuji tak, aby tato selhání nezmizela bez povšimnutí.
U akcí viditelných pro uživatele by aplikace měla oznámit, že data nebylo možné uložit.
U interních selhání by logy měly obsahovat dostatek informací pro diagnostiku příčiny.
Integrita dat
Pro zachování platného stavu používám společně databázová omezení a aplikační logiku.
Může jít například o:
- primární klíče,
- cizí klíče,
- unikátní indexy,
- požadavky na hodnoty non-null,
- transakční operace.
Důležitá pravidla pro data by neměla záviset výhradně na validaci v UI.
Databáze by měla chránit také svou vlastní integritu.
Chování při mazání
Odstranění jedné entity může ovlivnit související data.
Chování při mazání definuji záměrně.
V závislosti na vztahu může být správným řešením:
- kaskádové smazání,
- zákaz smazání,
- zachování závislých dat,
- vymazání reference.
Nechtěné kaskádové chování může odstranit více dat, než bylo zamýšleno, proto si vztahy zaslouží explicitní návrh.
Uživatelská data a obnova
U aplikací ukládajících informace vytvořené uživatelem považuji databázi za hodnotný uživatelský stav.
Destruktivní fallback strategie by se neměly používat bez rozmyslu.
Automatické smazání databáze kvůli chybějící migraci může být přijatelné u jednorázových dat cache.
U důležitých záznamů vytvořených uživatelem je to obvykle nepřijatelné.
Strategie perzistence by měla odpovídat hodnotě uložených informací.
Bezpečnost
Room automaticky nezajišťuje utajení obsahu lokální databáze.
Pokud aplikace ukládá citlivá data, zvažuji:
- co je skutečně nutné ukládat,
- ochrany poskytované operačním systémem,
- zda je potřeba dodatečné šifrování,
- zda přihlašovací údaje nepatří do jiného zabezpečeného úložiště.
Lokální databáze by se neměla stát pohodlným odkladištěm tajných údajů.
Testování
Databázové chování testuji tam, kde ovlivňuje důležitou aplikační logiku.
Testy mohou pokrývat:
- DAO dotazy,
- inserty,
- aktualizace,
- relace,
- transakce,
- migrace.
Protože Room ověřuje SQL při kompilaci, mnoho jednoduchých chyb je odhaleno brzy, ale logické chování je stále potřeba testovat.
KSP
Moderní vývoj s Room používá Kotlin Symbol Processing pro generování kódu.
Přirozeně zapadá do Android projektů založených na Kotlinu a v současné architektuře Room nahrazuje starší přístupy založené na annotation processingu.
Generovanou perzistentní infrastrukturu držím jako záležitost build procesu, zatímco aplikační kód se soustředí na entity, DAO a chování databáze.
Udržovatelnost
Jedním z hlavních důvodů, proč používám Room, je udržovatelnost.
Bez jasné architektury perzistence se databázová volání mohou snadno rozptýlit mezi:
- activities,
- fragments,
- composables,
- utility třídy.
Preferuji jasně definovanou cestu:
UI
→ ViewModel
→ Repository
→ DAO
→ Room
Je pak zřejmé, odkud data pocházejí a kam databázové operace patří.
Room v mém technologickém stacku
Room běžně používám společně s technologiemi, jako jsou:
- Android,
- Kotlin,
- Jetpack Compose,
- Kotlin Coroutines,
- Kotlin Flow,
- SQLite,
- DataStore,
- REST API,
- JSON,
- WorkManager.
Room poskytuje relační vrstvu lokálních dat, zatímco ostatní technologie řeší prezentaci, lehká nastavení, síťovou komunikaci a běh na pozadí.
Proč používám Room
Room používám proto, že strukturované Android aplikace potřebují více než ad hoc lokální úložiště.
Poskytuje silné propojení mezi aplikační architekturou v Kotlinu a SQLite pomocí typovaných entit, DAO, kontroly dotazů při kompilaci a podpory migrací.
Především však dává lokálním aplikačním datům jasnou architekturu.
Pro Android aplikace s podporou offline režimu, persistentní historii, data z externích zdrojů uložená do cache a další netriviální strukturované datové sady poskytuje Room databázovou vrstvu, která může zůstat srozumitelná a udržovatelná i s tím, jak aplikace roste.