Jetpack DataStore patří mezi technologie pro perzistenci v Androidu, které používám ve chvíli, kdy aplikace potřebuje spolehlivé lokální ukládání preferencí, nastavení nebo menších strukturovaných dat.
Používám ho především jako moderní náhradu za SharedPreferences a jako součást Android architektury založené na Kotlinu, kde má být aplikační stav zpřístupněn reaktivně prostřednictvím Flow a aktualizován asynchronně pomocí coroutines.
Pro mě DataStore není jen místo pro uložení několika hodnot.
Je součástí způsobu, jakým navrhuji předvídatelný aplikační stav, který přežije restart procesu a zůstává synchronizovaný s uživatelským rozhraním.
Jak používám DataStore
DataStore používám například pro:
- uživatelské preference,
- nastavení aplikace,
- volby funkcí,
- stav onboardingu,
- konfiguraci UI,
- vybrané režimy,
- lokální příznaky,
- lehký perzistentní aplikační stav,
- cachované informace o oprávněních,
- preference chování aplikace.
Konkrétní implementace závisí na tom, jak strukturovaná mají uložená data být.
Pro jednoduchá nastavení typu key-value často postačuje Preferences DataStore.
Pro silně typovaný strukturovaný stav mohu použít typovaný DataStore s explicitním serializačním formátem.
Náhrada SharedPreferences
Jedním z hlavních důvodů, proč používám DataStore, je modernější model perzistence oproti tradičním SharedPreferences.
DataStore je navržen kolem:
- asynchronního přístupu,
- Kotlin coroutines,
- Flow,
- konzistentních aktualizací,
- transakčních zápisů.
To mnohem lépe zapadá do moderní Android architektury než synchronní přístup k preferencím rozptýlený napříč aktivitami nebo composable funkcemi.
Místo ručního načítání hodnot při každém vytvoření obrazovky může aplikace sledovat perzistentní stav jako datový proud.
Preferences DataStore
Preferences DataStore je vhodný tehdy, když aplikace potřebuje jednoduché úložiště typu key-value.
Typickými příklady jsou:
- tmavý režim,
- zvolený jazyk,
- nastavení notifikací,
- feature toggles,
- dokončení onboardingu,
- preferované jednotky.
Ukládané hodnoty zůstávají jednoduché, zatímco DataStore řeší jejich perzistenci asynchronně.
Používám jasně definované klíče a přímý přístup k DataStore obvykle skrývám za repository nebo komponentu nastavení místo toho, abych preference keys zpřístupňoval napříč celou aplikací.
Typovaný DataStore
Pokud má perzistentní stav smysluplnější strukturu, může typovaný DataStore nabídnout čistší model.
Místo správy nezávislých klíčů aplikace ukládá strukturovaný objekt.
To může zpřehlednit nastavení v situacích, kdy více vlastností logicky patří k sobě.
Typované ukládání zároveň poskytuje silnější kontrakt mezi perzistentními daty a aplikačním kódem.
Podle projektu může být serializace založená například na Protocol Buffers nebo jiném explicitním serializeru.
Proto DataStore
Proto DataStore je užitečný ve chvíli, kdy chci silně typovaný perzistentní stav s definovaným schématem.
Schéma může například popisovat nastavení:
UserSettings
├── dark_mode
├── notifications_enabled
├── preferred_language
└── selected_mode
Generované typy dělají uloženou strukturu explicitní.
Tím se snižuje riziko chyb způsobených:
- překlepy v textových klíčích,
- nesprávnými datovými typy,
- nekonzistentními výchozími hodnotami.
U strukturovanější konfigurace tento přístup preferuji před stále větší kolekcí nesouvisejících preference keys.
Kotlin Coroutines
DataStore je navržen pro asynchronní operace.
Při čtení a aktualizaci perzistentních dat používám Kotlin coroutines, aby práce s úložištěm neblokovala hlavní vlákno aplikace.
To přirozeně zapadá do zbytku Android architektury založené na coroutines.
Repository pro nastavení může zpřístupňovat perzistentní hodnoty, zatímco obrazovky se soustředí pouze na vykreslování stavu.
Kotlin Flow
Flow je jedním z hlavních důvodů, proč DataStore tak dobře zapadá do moderního vývoje Android aplikací.
Uložená data lze zpřístupnit jako Flow, takže zbytek aplikace může automaticky reagovat na změny hodnot.
Koncepčně:
DataStore
→ Flow
→ ViewModel
→ UI State
→ Jetpack Compose
Když se preference změní, nová hodnota se propaguje celou architekturou.
UI nemusí úložiště opakovaně pollovat.
DataStore s Jetpack Compose
DataStore funguje obzvlášť dobře s Jetpack Compose.
Perzistentní nastavení obvykle zpřístupňuji přes ViewModel nebo jinou stavovou vrstvu a nechávám Compose sledovat výsledný stav.
Pokud například uživatel změní téma aplikace, může se tato hodnota zapsat do DataStore.
Změna následně projde přes ViewModel a způsobí automatickou recomposition příslušných composable funkcí.
Vzniká tak čisté oddělení:
Compose UI
→ akce uživatele
→ ViewModel / repository
→ DataStore
DataStore
→ Flow
→ ViewModel
→ Compose UI
UI nemusí vědět, jak je nastavení fyzicky uložené.
Repository pattern
Přístup k DataStore obecně držím za samostatným repository nebo abstrakcí pro nastavení.
Místo toho, aby nesouvisející části aplikace přímo četly a zapisovaly preference keys, zpřístupňuji operace typu:
setDarkMode()
setPreferredLanguage()
setNotificationsEnabled()
a pozorovatelný stav, například:
settingsFlow
Perzistentní vrstva tak má jasně definované rozhraní.
Zároveň se tím usnadňují případné budoucí změny způsobu ukládání dat.
Single Source of Truth
U perzistentních nastavení preferuji jeden jasný zdroj pravdy.
Nezávislé duplikování stejného stavu v:
- DataStore,
- proměnných ViewModelu,
- composable stavu,
- statických objektech
může snadno vytvářet nekonzistence.
Pokud DataStore reprezentuje perzistentní stav, dočasné UI reprezentace by z něj měly vycházet nebo se s ním synchronizovat prostřednictvím jasně definované architektury.
Díky tomu je chování aplikace snazší pochopit.
Výchozí hodnoty
Aplikace potřebuje předvídatelné chování ještě předtím, než byla konkrétní hodnota vůbec někdy uložená.
Pro perzistentní preference proto definuji explicitní výchozí hodnoty.
Například:
darkMode = false
notificationsEnabled = true
Tím je zajištěno konzistentní chování při prvním spuštění místo závislosti na null nebo nedefinovaném stavu.
Aktualizace dat
Aktualizace v DataStore jsou transakční.
Používám jeho aktualizační mechanismy místo ručního čtení souboru, změny hodnot a následného zápisu celého stavu zpět z nesouvisejícího kódu.
U strukturovaného DataStore transformační operace převádí existující hodnotu na nový immutable stav.
Tím se snižuje riziko konfliktních nebo částečně zapsaných aktualizací.
Immutable stav
Pro nastavení aplikace preferuji immutable modely.
Místo změny sdíleného objektu nastavení přímo na místě vytváří aktualizace nový stav.
To přirozeně zapadá do:
- DataStore,
- Kotlinu,
- Flow,
- Jetpack Compose.
Immutable stav usnadňuje sledování datového toku a omezuje nenápadné problémy se synchronizací.
Zpracování chyb
Perzistentní úložiště může selhat.
Proto zohledňuji chyby, jako jsou:
- poškozená uložená data,
- selhání čtení,
- problémy se serializací,
- neočekávaný stav migrace.
Aplikace by tyto situace měla řešit záměrně místo pádu při spuštění kvůli tomu, že nelze přečíst jeden soubor s preferencemi.
Kde je to vhodné, používám bezpečné výchozí hodnoty nebo explicitní zpracování poškozených dat.
Migrace ze SharedPreferences
Existující Android aplikace mohou už preference ukládat pomocí SharedPreferences.
Při modernizaci takového projektu mohu existující hodnoty migrovat do DataStore místo toho, aby uživatelé přišli o svá nastavení.
Migrační strategie musí zachovat význam původních hodnot a správně je namapovat do nového datového modelu.
Po úspěšné migraci může aplikace dál používat DataStore jako primární mechanismus perzistence.
Vývoj schématu
Typovaný perzistentní stav se může vyvíjet spolu s přibývající funkcionalitou aplikace.
Například raná verze může obsahovat:
theme
language
zatímco pozdější verze přidá:
notifications
default_screen
accessibility_preferences
Výchozí hodnoty a serializaci navrhuji tak, aby bylo možné bezpečně interpretovat i starší uložený stav.
Perzistentní aplikační data by se měla vyvíjet společně se softwarem místo toho, aby se stala překážkou budoucího vývoje.
DataStore vs. Room
DataStore a Room používám pro rozdílné typy problémů.
DataStore je vhodný pro:
- preference,
- nastavení,
- menší strukturovaný stav,
- jednoduché perzistentní hodnoty.
Room je vhodnější pro:
- velké datasety,
- více vzájemně propojených entit,
- složité dotazy,
- částečné aktualizace záznamů,
- relační integritu,
- historii aplikace.
Nesnažím se z DataStore dělat náhradu relační databáze.
Volba správné perzistentní vrstvy udržuje architekturu jednodušší.
DataStore vs. SQLite
SQLite poskytuje relační databázový engine.
DataStore poskytuje lehký perzistentní aplikační stav.
Pokud potřebuji:
users → records → categories → history
obvykle použiji Room nebo SQLite.
Pokud potřebuji:
theme = dark
language = cs
notifications = enabled
DataStore je zpravidla čistší řešení.
Technologie pro ukládání dat by měla odpovídat složitosti samotných dat.
Lokální aplikační stav
DataStore je užitečný pro hodnoty, které musí přežít ukončení procesu, aniž by vyžadovaly vzdálený server.
Aplikace si tak může pamatovat například:
- preference uživatelského rozhraní,
- dříve vybrané možnosti,
- dokončený onboarding,
- lokální konfiguraci funkcí.
Tím se zlepšuje kontinuita mezi jednotlivými spuštěními aplikace.
Offline chování
Protože je DataStore lokální, aplikace nepotřebuje síťové připojení pro přístup k těmto nastavením.
To je užitečné pro Android aplikace, které se mají chovat předvídatelně i v situacích, kdy jsou:
- offline,
- na nestabilním mobilním připojení,
- bez možnosti spojit se s backendem.
Základní preference aplikace by obecně neměly být zbytečně závislé na vzdálené službě.
Stav monetizace
V aplikacích s monetizací mohu DataStore používat pro cachovaný lokální stav související s uživatelskou zkušeností.
Může si například pamatovat, že aplikace dříve zaznamenala premium nebo remove-ads oprávnění.
Pečlivě ale rozlišuji mezi:
- cachovaným lokálním stavem,
- autoritativním billing stavem.
Skutečným zdrojem pravdy o nákupu může stále být Google Play Billing nebo backend.
DataStore může zlepšit chování při spuštění aplikace, neměl by ale automaticky nahrazovat ověření nákupu.
Preference reklam
DataStore může být součástí stavu souvisejícího s monetizací, například:
- zda už byla zobrazena onboardingová zpráva,
- uživatelské preference týkající se reklam,
- aplikačně specifický stav monetizačního UI.
Souhlas s reklamou citlivý z hlediska soukromí by ale stále měl respektovat požadavky příslušné consent platformy a SDK.
Obecný lokální příznak nepoužívám jako náhradu skutečného consent systému.
Obrazovky nastavení
DataStore je obzvlášť užitečný jako podklad pro obrazovky nastavení aplikace.
Uživatel může změnit volbu a tato změna se může:
- okamžitě aktualizovat,
- lokálně uložit,
- propagovat do zbytku aplikace.
S Flow a Compose může stejný stav řídit jak rozhraní nastavení, tak funkci, kterou dané nastavení ovlivňuje.
Tím se vyhýbám udržování duplicitního stavu pro stejnou preferenci.
Aplikační architektura
Typická architektura, kterou používám, může vypadat takto:
Jetpack Compose
↓
ViewModel
↓
Settings Repository
↓
DataStore
a perzistentní změny se propagují zpět opačným směrem:
DataStore
↓
Flow
↓
ViewModel
↓
Compose UI
Každá vrstva má jasně definovanou odpovědnost.
Díky tomu lze mechanismus perzistence případně nahradit, aniž by byla každá obrazovka přímo svázaná s DataStore.
Dependency Injection
Ve větších Android aplikacích lze repository postavené nad DataStore poskytovat prostřednictvím dependency injection.
Tím se zabrání vytváření instancí úložiště na náhodných místech v codebase a pomáhá to udržet konzistentní aplikační scope.
Zároveň se zjednodušuje testování, protože perzistentní abstrakci lze nahradit testovací implementací.
Jedna instance DataStore na soubor
Vlastnictví instancí DataStore držím explicitní.
Více nezávislých instancí směřujících na stejný soubor úložiště může způsobit nesprávné chování.
Centralizované vytváření na úrovni aplikace nebo dependency injection tomuto problému předchází a jasně definuje životní cyklus perzistentní vrstvy.
Testování
Oddělení DataStore za abstrakci zlepšuje také testování.
Aplikační logiku lze testovat proti:
- dočasnému úložišti,
- testovacím repository,
- předdefinovaným stavům nastavení.
Funkcionalitu tak lze testovat bez zbytečné závislosti na skutečném perzistentním prostředí.
DataStore a Android lifecycle
Perzistentní preference by neměly záviset na existenci konkrétní Activity nebo obrazovky.
DataStore proto držím na aplikační vrstvě místo jeho vytváření uvnitř jednotlivých UI komponent.
Stav díky tomu odolává:
- navigaci,
- znovuvytvoření Activity,
- konfiguračním změnám,
- událostem životního cyklu procesu.
Rozhraní může zmizet a být znovu vytvořeno, zatímco podkladové perzistentní nastavení zůstává zachované.
Výkon
DataStore je určen pro relativně malé množství aplikačního stavu.
Ukládané struktury udržuji úzce zaměřené a nepoužívám ho jako kontejner pro rozsáhlé kolekce aplikačních záznamů.
Tím zůstávají čtení i aktualizace předvídatelné.
Pokud data narostou do podoby, která vyžaduje složité dotazy nebo časté částečné změny, přesouvám tuto odpovědnost do Room nebo jiné databázové vrstvy.
Bezpečnost
DataStore by neměl být automaticky považován za bezpečné úložiště tajných údajů.
Pokud aplikace potřebuje ukládat citlivé přístupové údaje nebo kryptografická tajemství, zvažuji mechanismy navržené přímo pro tento účel.
DataStore je vhodný pro aplikační stav a preference.
Strategii ukládání by měly určovat bezpečnostní požadavky konkrétních dat.
Zálohování a migrace zařízení
Perzistentní lokální data mohou interagovat s mechanismy Androidu pro zálohování a přenos mezi zařízeními.
Zvažuji, zda mají být konkrétní nastavení zachována v situacích, kdy uživatel:
- aplikaci znovu nainstaluje,
- přejde na jiné zařízení,
- obnoví data ze zálohy.
Ne každý typ lokálního stavu musí nutně používat stejnou politiku zálohování.
DataStore v mém technologickém stacku
DataStore běžně používám společně s technologiemi, jako jsou:
- Android,
- Kotlin,
- Jetpack Compose,
- Kotlin Coroutines,
- Kotlin Flow,
- Room,
- Google Play Billing,
- Google Mobile Ads / AdMob.
V rámci širší Android architektury poskytuje lehkou vrstvu perzistentního aplikačního stavu.
Proč používám DataStore
DataStore používám proto, že moderní Android aplikace potřebují předvídatelný způsob ukládání menšího množství stavu bez blokování UI nebo rozptylování synchronního přístupu k preferencím napříč codebasem.
Jeho kombinace asynchronního ukládání, Kotlin Flow a transakčních aktualizací přirozeně zapadá do moderního vývoje Android aplikací.
Nejdůležitější ale je, že ho používám pro typ dat, pro který byl navržen.
Preference a lehký aplikační stav patří do DataStore.
Komplexní relační aplikační data patří do Room nebo jiné databáze.
Jasné zachování této hranice vede k jednodušším a udržitelnějším Android aplikacím.