DataStore — komponent z knihovny Jetpack určený pro ukládání malých objemů dat v Android aplikacích. Na rozdíl od SharedPreferences pracuje asynchronně a zaručuje konzistenci dat při konkurentním přístupu. Podle údajů Google, 2024 používá DataStore Kotlin Coroutines a Flow, což jej činí bezpečným pro hlavní vlákno a vhodným pro reaktivní architektury.
Hlavní body
DataStore — řešení od Google pro lokální ukládání dat v Android, představené v roce 2020 jako alternativa k SharedPreferences. Podporuje dva režimy: Preferences DataStore (jednoduché páry klíč-hodnota) a Proto DataStore (typované schéma založené na Protocol Buffers).
Hlavní výhoda — plná asynchronnost: všechny operace čtení vracejí Flow z Kotlin Coroutines a zápis se provádí v kontextu coroutine. To eliminuje blokování hlavního vlákna, které bylo typickým problémem SharedPreferences při práci s velkými objemy dat.
DataStore zaručuje atomicitu operací: konkurentní zápisy nevedou ke ztrátě dat díky transakčnímu modelu. Pokud dvě komponenty současně mění stejnou hodnotu, DataStore správně zpracuje konflikt pomocí mechanismu compare-and-swap.
Podle údajů Google I/O 2023 se DataStore používá ve 40 % nových projektů na Android a Google doporučuje migraci ze SharedPreferences ve všech aplikacích, kde je vyžadována stabilita ukládání nastavení.
Základem DataStore je SingleProcessDataStore — implementace pracující v rámci jednoho procesu. Používá souborové úložiště se zámky na úrovni souboru: při zápisu dat je soubor uzamčen, což zabraňuje poškození při konkurentním přístupu.
DataStore automaticky zpracovává chyby deserializace: pokud je soubor poškozen, vrátí výchozí hodnotu a přepíše soubor. Toto chování se konfiguruje přes corruptionHandler, který lze nastavit při vytváření DataStore.
SharedPreferences trpí třemi zásadními problémy: synchronní čtení z disku na hlavním vlákně, absence záruk atomity při konkurentních zápisech a nemožnost reaktivního sledování změn. DataStore řeší všechny tři: Flow pro pozorování, zámek souboru pro atomicitu a asynchronní API pro bezpečnost vláken.
DataStore ukládá data do souborů ve vnitřní paměti zařízení. Preferences DataStore používá formát souboru podobný SharedPreferences, ale s dodatečnými metadaty pro kontrolu integrity. Proto DataStore používá binární formát Protocol Buffers, což zmenšuje velikost souboru a zrychluje serializaci.
Při čtení dat DataStore načte celý soubor do paměti jednorázově, poté odběratelé obdrží aktuální stav přes Flow. Změny jsou automaticky přenášeny všem aktivním odběratelům — není vyžadována ruční registrace listenerů, jako u SharedPreferences.
Preferences DataStore používá vestavěný serializační mechanismus založený na Map. Každý záznam je pár řetězce a primitivního typu (Int, Boolean, Float, Long, String, Set). Data jsou uložena v XML souboru, podobně jako SharedPreferences, ale s atomickým zápisem přes zámek souboru.
Příklad vytvoření Preferences DataStore: rozšíření preferencesDataStore na Context vytvoří singleton s názvem souboru. Při opakovaných voláních se vrací stejná instance — to eliminuje duplikování souborů a záměnu s různými instancemi úložiště.
Proto DataStore vyžaduje definování datového schématu přes .proto soubor a kompilaci pomocí protobuf pluginu. Generovaná Java třída se používá jako jediný vstupní bod pro všechna pole — to eliminuje překlepy v klíčích, typické pro SharedPreferences.
Schéma Proto DataStore se definuje jednou a podporuje přidávání nových polí bez ztráty starých dat. Pokud se v nové verzi aplikace přidá pole s výchozí hodnotou, starý soubor bude správně deserializován — zpětná kompatibilita je zabudována do protokolu.
Výběr mezi Preferences DataStore a Proto DataStore závisí na složitosti dat a požadavcích na typování. Obě varianty jsou asynchronní a transakční, ale liší se úrovní type-safety a výkonem serializace.
| Vlastnost | Preferences DataStore | Proto DataStore |
|---|---|---|
| Typování | Slabé (klíč-hodnota) | Silné (generovaná třída) |
| Serializace | XML (vestavěná) | Protocol Buffers (protobuf) |
| Velikost souboru | Velká (čitelné XML) | Malá (binární) |
| Složitost | Nízká (bez .proto) | Střední (vyžaduje .proto) |
| Migrace schématu | Žádné schéma | Automatická (proto) |
| Kompatibilita | SharedPreferences (přes migraci) | Pouze Proto DataStore |
Preferences DataStore je vhodný pro jednoduchá nastavení: příznaky zapnutí funkcí, řetězec autorizačního tokenu, počet spuštění aplikace. Pokud je dat málo (do 10–15 klíčů) a nevyžadují přísné schéma — Preferences DataStore poskytuje minimální práh vstupu bez připojování protobuf pluginu.
Proto DataStore je opodstatněný, když je datová struktura složitá nebo se může měnit mezi verzemi aplikace. Například nastavení profilu uživatele nebo konfigurace A/B testů s 20+ poli. Protobuf poskytuje silné typování a automatické migrace, což eliminuje chyby za běhu kvůli neshodě klíčů.
Google poskytuje vestavěný mechanismus migrace přes třídu SharedPreferencesMigration. Migrace se provádí jednorázově při prvním spuštění po aktualizaci aplikace: DataStore načte data ze SharedPreferences, zapíše je do svého formátu a označí migraci jako dokončenou.
Migrace podporuje vlastní transformace: pokud se klíče v SharedPreferences neshodují s požadovanými klíči DataStore, lze definovat transformační funkci přes SharedPreferencesMigration. To umožňuje přejmenovávání klíčů a změnu datových typů v procesu migrace.
Prvním krokem přidejte DataStore do build.gradle a vytvořte instanci DataStore s migrací: SharedPreferencesMigration přijímá název SharedPreferences souboru a sadu klíčů k přenesení. Druhým krokem odstraňte veškerý kód pracující přes SharedPreferences a nahraďte jej voláními DataStore. Třetím — otestujte migraci: při prvním spuštění by se data měla objevit v DataStore a starý SharedPreferences soubor by měl přestat být používán.
val Context.dataStore by preferencesDataStore(
name = "settings",
produceMigrations = { context ->
listOf(
SharedPreferencesMigration(context, "old_prefs")
)
}
)
DataStore se snadno integruje do existujícího projektu. Níže jsou uvedeny praktické příklady pro Preferences DataStore a Proto DataStore — oba demonstrují čtení, zápis a reaktivní pozorování dat.
V tomto příkladu Preferences DataStore ukládá tři nastavení: tmavý motiv, uživatelské jméno a počet spuštění. Čtení se provádí přes rozšíření .data, které vrací Flow. Zápis — přes suspend funkci .edit, která zaručuje atomicitu změn.
val Context.settingsDataStore by preferencesDataStore(name = "settings")
val isDarkMode: Flow<Boolean> = settingsDataStore.data
.map { preferences ->
preferences[booleanPreferencesKey("dark_mode")] ?: false
}
suspend fun toggleDarkMode() {
settingsDataStore.edit { prefs ->
val current = prefs[booleanPreferencesKey("dark_mode")] ?: false
prefs[booleanPreferencesKey("dark_mode")] = !current
}
}
Proto DataStore vyžaduje definici .proto souboru. Po kompilaci je vytvořena třída UserSettings, která se používá pro čtení a zápis. Migrace verzí schématu jsou popsány ve stejném .proto souboru a aplikovány automaticky.
// user_preferences.proto
syntax = "proto3";
message UserPreferences {
string display_name = 1;
int32 notification_count = 2;
bool notifications_enabled = 3;
}
// Čtení z DataStore
val userPreferencesFlow: Flow<UserPreferences> =
protoDataStore.data
// Zápis nových hodnot
suspend fun updateDisplayName(name: String) {
protoDataStore.updateData { prefs ->
prefs.toBuilder()
.setDisplayName(name)
.build()
}
}
DataStore se integruje s architekturou MVVM přes ViewModel. Flow z DataStore je sbírán přes .stateIn a používán v UI. Při každé změně dat se UI automaticky aktualizuje — nejsou potřeba ruční aktualizace ani LiveData.
class SettingsViewModel(
private val dataStore: DataStore<Preferences>
) : ViewModel() {
val uiState: StateFlow<SettingsUiState> =
dataStore.data
.map { prefs ->
SettingsUiState(
isDarkMode = prefs[booleanPreferencesKey("dark_mode")] ?: false,
counter = prefs[intPreferencesKey("launch_count")] ?: 0
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = SettingsUiState()
)
}
Často kladené otázky
DataStore pracuje asynchronně (neblokuje UI vlákno), podporuje konkurentní přístup přes transakce a umožňuje reaktivní sledování změn přes Flow. SharedPreferences — synchronní API s rizikem ANR při velkých objemech dat a bez vestavěné podpory reaktivity.
DataStore je napsán v Kotlinu a vyžaduje Kotlin Coroutines. Použití z Javy je možné, ale nepohodlné: je třeba vytvářet obálky s CompletableFuture nebo ručně spravovat coroutines. Pro Java projekty Google doporučuje ponechat SharedPreferences nebo přidat Kotlin do modulu.
DataStore načítá celý soubor do paměti při čtení, proto není vhodný pro ukládání seznamů nebo velkých objektů. Pro takové scénáře použijte Room nebo SQLite. DataStore je optimalizován pro nastavení a malá strukturovaná data — až stovky kilobajtů.
Při vytváření DataStore lze předat corruptionHandler — funkci volanou při poškození souboru. Ve výchozím nastavení DataStore vyhazuje výjimku CorruptionException. V corruptionHandler lze vrátit prázdná data, poté DataStore přepíše soubor správným stavem.
Ano, Proto DataStore vyžaduje definici schématu v .proto souboru a připojení protobuf-gradle-plugin. Pokud je projekt malý a data jednoduchá, je snazší použít Preferences DataStore — nevyžaduje dodatečnou konfiguraci sestavení.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také