DataStore slúži na perzistovanie malých dát. Perzistovanie znamená, že dáta, ktoré si do DataStore uložím, zostanú k dispozícii aj po aplikácii sa ukončí/reštartuje. Ukladáme tam veci ako:
Dáta sa ukladajú štýlom kľúč hodnota. Teda napríklad pod kľúčom "DARK_MODE bude uložená hodnota true alebo false.
Vždy keď si vyžiadam hodnotu pod kľúčom "DARK_MODE", DataStore vráti hodnotu, ktorá tam bola uložená naposledy.
Dáta z DataStore Preferences sa za normálnych okolností neprenášajú medzi inštaláciami aplikácie.
Pridáme závislosť do súboru app/build.gradle.kts:
dependencies {
implementation("androidx.datastore:datastore-preferences:1.2.1")
}
Kľúče definujeme tak, že priamo podľa kľúča vieme zistiť, aký typ dát sa nachádza v hodnote. Tieto kľúče ako aj zvyšok
konfigurácie odporúčam držať v samostatnom súbore. Napr. preferences.kt. Zopár praktických príkladov:
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val EXAMPLE_TOKEN = stringPreferencesKey("example_token")
val EXAMPLE_ONBOARDING_PASSED = booleanPreferencesKey("example_onboarding_passed")
val EXAMPLE_LAST_UPDATE_MILLIS = longPreferencesKey("example_last_update_millis")
DataStore preferences sú na pozadí uložené v nejakom súbore. Keď chceme získať DataStore pre prácu s preferences,
použijeme preferencesDataStore s názvom tohto súboru (názov je na nás):
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
Ak chceme získať Context v Composable funkcii, voláme LocalContext.current. Napríklad nasledovne:
@Composable
fun DataStoreSample() {
val context = LocalContext.current
val dataStore = context.dataStore
}
Pre prácu s Preferences DataStore si rád definujem pomocné funkcie, lebo práca s ním môže byť nepríjemná:
fun <T> DataStore<Preferences>.observe(key: Preferences.Key<T>): Flow<T?> = data.map { prefs -> prefs[key] }
suspend fun <T> DataStore<Preferences>.set(key: Preferences.Key<T>, value: T) = updateData { prefs ->
prefs.toMutablePreferences().apply { set(key, value) }
}
suspend fun <T> DataStore<Preferences>.clear(key: Preferences.Key<T>) = updateData { prefs ->
prefs.toMutablePreferences().apply { remove(key) }
}
Všimnime si, že funkcia observe vracia Flow a nie priamo hodnotu. To znamená jednoducho to, že akonáhle sa zmení hodnota v DataStore,
funkcia observe okamžite vráti novú hodnotu. Môžeme tak okamžite aktualizovať UI pri zmene hodnoty v DataStore.
Ukážka na počúvanie zmien v DatasStore:
@Composable
fun DataStoreSample() {
val context = LocalContext.current
val dataStore = context.dataStore
val counter by dataStore.observe(EXAMPLE_COUNTER).collectAsState(initial = 0)
val token by dataStore.observe(EXAMPLE_TOKEN).collectAsState(initial = "")
val onboardingPassed by dataStore.observe(EXAMPLE_ONBOARDING_PASSED).collectAsState(initial = false)
val lastUpdateMillis by dataStore.observe(EXAMPLE_LAST_UPDATE_MILLIS).collectAsState(initial = 0L)
// Tento Text sa aktualizuje pri zmene ktorejkolvek zo 4 hodnot v riadkoch vyssie
Text(
"Counter: $counter, \n" +
"Token: $token, \n" +
"Onboarding Passed: $onboardingPassed, \n" +
"Last Update: $lastUpdateMillis\n"
)
}
Ukážka na zapisovanie hodnot do Preferences DataStore - pre zapisovanie potrebujeme volať suspend funkciu, preto
musíme použiť rememberCoroutineScope (alebo s nimi pracovať vo ViewModel):
@Composable
fun DataStoreSample() {
val context = LocalContext.current
val dataStore = context.dataStore
val coroutineScope = rememberCoroutineScope()
Button(onClick = {
coroutineScope.launch {
dataStore.set(EXAMPLE_COUNTER, 0)
}
}) {
Text("Clear Counter")
}
}
Akonáhle by sme chceli riešiť napríklad inkrementáciu hodnoty, mnou definované pomocné funkcie by nestačili. Tie totiž nepoznajú predchádzajúcu hodnotu. Buď si nechaj riešenie vygenerovať, alebo sa mi ozvi :)