DataStore: co to jest, podstawy przechowywania danych i zamiennik SharedPreferences

Autor: IT Sectr Opublikowano: 2026-03-12 Czas czytania: 9 min

DataStore — komponent z biblioteki Jetpack przeznaczony do przechowywania niewielkich ilości danych w aplikacjach Android. W przeciwieństwie do SharedPreferences działa asynchronicznie i gwarantuje spójność danych przy współbieżnym dostępie. Według danych Google, 2024, DataStore wykorzystuje Kotlin Coroutines i Flow, co czyni go bezpiecznym dla głównego wątku i odpowiednim dla architektur reaktywnych.

Najważniejsze

  • DataStore — zamiennik SharedPreferences z asynchronicznym API i typami danych przez Protocol Buffers
  • Preferences DataStore — prosty Key-Store z odczytem przez Flow i transakcjami
  • Proto DataStore — typowane repozytorium z automatycznymi migracjami schematu
  • SharedPreferences — synchroniczne API blokujące wątek UI przy dużych woluminach
  • Migracja wykonywana przez specjalny interfejs SharedPreferencesMigration bez utraty danych

Co to jest DataStore?

DataStore — rozwiązanie od Google do lokalnego przechowywania danych w Android, przedstawione w 2020 roku jako alternatywa dla SharedPreferences. Obsługuje dwa tryby: Preferences DataStore (proste pary klucz-wartość) i Proto DataStore (typowany schemat oparty na Protocol Buffers).

Główna zaleta — pełna asynchroniczność: wszystkie operacje odczytu zwracają Flow z Kotlin Coroutines, a zapis wykonywany jest w kontekście coroutine. Eliminuje to blokowanie głównego wątku, które było typowym problemem SharedPreferences przy pracy z dużymi woluminami danych.

DataStore gwarantuje atomowość operacji: współbieżne zapisy nie prowadzą do utraty danych dzięki modelowi transakcyjnemu. Jeśli dwa komponenty jednocześnie zmieniają tę samą wartość, DataStore poprawnie obsługuje konflikt przez mechanizm compare-and-swap.

Według danych Google I/O 2023, DataStore jest używany w 40% nowych projektów na Android, a Google zaleca migrację z SharedPreferences we wszystkich aplikacjach wymagających stabilności przechowywania ustawień.

Architektura DataStore

U podstaw DataStore leży SingleProcessDataStore — implementacja działająca w ramach jednego procesu. Wykorzystuje pamięć plikową z blokadami na poziomie pliku: podczas zapisu danych plik jest blokowany, co zapobiega uszkodzeniu przy współbieżnym dostępie.

DataStore automatycznie obsługuje błędy deserializacji: jeśli plik jest uszkodzony, zwraca wartość domyślną i nadpisuje plik. To zachowanie jest konfigurowane przez corruptionHandler, który można ustawić przy tworzeniu DataStore.

Problemy SharedPreferences rozwiązywane przez DataStore

SharedPreferences cierpi na trzy fundamentalne problemy: synchroniczny odczyt z dysku na głównym wątku, brak gwarancji atomowości przy współbieżnych zapisach i niemożliwość reaktywnego śledzenia zmian. DataStore rozwiązuje wszystkie trzy: Flow do obserwacji, blokada plikowa dla atomowości i asynchroniczne API dla bezpieczeństwa wątków.

Jak działa DataStore w Android?

DataStore przechowuje dane w plikach w wewnętrznej pamięci urządzenia. Preferences DataStore używa formatu pliku podobnego do SharedPreferences, ale z dodatkowymi metadanymi do sprawdzania integralności. Proto DataStore używa binarnego formatu Protocol Buffers, co zmniejsza rozmiar pliku i przyspiesza serializację.

Podczas odczytu danych DataStore ładuje cały plik do pamięci jednorazowo, po czym subskrybenci otrzymują aktualny stan przez Flow. Zmiany są transmitowane do wszystkich aktywnych subskrybentów automatycznie — nie wymaga ręcznej rejestracji listenerów, jak w SharedPreferences.

Zasada działania Preferences DataStore

Preferences DataStore wykorzystuje wbudowany mechanizm serializacji oparty na Map. Każdy wpis to para ciągu znaków i typu prostego (Int, Boolean, Float, Long, String, Set). Dane są przechowywane w pliku XML, podobnym do SharedPreferences, ale z atomowym zapisem przez blokadę plikową.

Przykład tworzenia Preferences DataStore: rozszerzenie preferencesDataStore na Context tworzy singleton z nazwą pliku. Przy wielokrotnych wywołaniach zwracana jest ta sama instancja — eliminuje to duplikowanie plików i pomyłki z różnymi instancjami repozytorium.

Zasada działania Proto DataStore

Proto DataStore wymaga zdefiniowania schematu danych przez plik .proto i kompilacji za pomocą protobuf-pluginu. Wygenerowana klasa Java jest używana jako jedyny punkt wejścia dla wszystkich pól — eliminuje to literówki w kluczach typowe dla SharedPreferences.

Schemat Proto DataStore jest definiowany raz i obsługuje dodawanie nowych pól bez utraty starych danych. Jeśli w nowej wersji aplikacji doda się pole z wartością domyślną, stary plik zostanie poprawnie zdeserializowany — wsteczna kompatybilność jest wbudowana w protokół.

Preferences DataStore i Proto DataStore: porównanie

Wybór między Preferences DataStore a Proto DataStore zależy od złożoności danych i wymagań dotyczących typowania. Oba warianty są asynchroniczne i transakcyjne, ale różnią się poziomem type-safety i wydajnością serializacji.

CechaPreferences DataStoreProto DataStore
TypowanieSłabe (klucz-wartość)Silne (wygenerowana klasa)
SerializacjaXML (wbudowana)Protocol Buffers (protobuf)
Rozmiar plikuDuży (czytelny XML)Mały (binarny)
ZłożonośćNiska (bez .proto)Średnia (wymaga .proto)
Migracja schematuBrak schematuAutomatyczna (proto)
KompatybilnośćSharedPreferences (przez migrację)Tylko Proto DataStore

Kiedy wybrać Preferences DataStore

Preferences DataStore nadaje się do prostych ustawień: flagi włączania funkcji, ciąg tokena autoryzacji, liczba uruchomień aplikacji. Jeśli danych jest mało (do 10–15 kluczy) i nie wymagają ścisłego schematu — Preferences DataStore daje minimalny próg wejścia bez podłączania protobuf-pluginu.

Kiedy wybrać Proto DataStore

Proto DataStore jest uzasadniony, gdy struktura danych jest złożona lub może się zmieniać między wersjami aplikacji. Na przykład ustawienia profilu użytkownika czy konfiguracja testów A/B z 20+ polami. Protobuf daje silne typowanie i automatyczne migracje, co eliminuje błędy wykonawcze z powodu niezgodności kluczy.

Jak migrować z SharedPreferences na DataStore

Google udostępnia wbudowany mechanizm migracji przez klasę SharedPreferencesMigration. Migracja wykonywana jest jednorazowo przy pierwszym uruchomieniu po aktualizacji aplikacji: DataStore odczytuje dane z SharedPreferences, zapisuje je w swoim formacie i oznacza migrację jako zakończoną.

Migracja obsługuje niestandardowe transformacje: jeśli w SharedPreferences klucze nie pasują do pożądanych kluczy DataStore, można zdefiniować funkcję przekształcającą przez SharedPreferencesMigration. Pozwala to zmieniać nazwy kluczy i typy danych w procesie migracji.

Migracja krok po kroku

Pierwszym krokiem dodaj DataStore do build.gradle i utwórz instancję DataStore z migracją: SharedPreferencesMigration przyjmuje nazwę pliku SharedPreferences i zestaw kluczy do przeniesienia. Drugim krokiem usuń cały kod działający przez SharedPreferences i zastąp go wywołaniami DataStore. Trzecim — przetestuj migrację: przy pierwszym uruchomieniu dane powinny pojawić się w DataStore, a stary plik SharedPreferences przestać być używany.

kotlin
val Context.dataStore by preferencesDataStore(
    name = "settings",
    produceMigrations = { context ->
        listOf(
            SharedPreferencesMigration(context, "old_prefs")
        )
    }
)

Przykłady użycia DataStore w kodzie

DataStore łatwo integruje się z istniejącym projektem. Poniżej przedstawiono praktyczne przykłady dla Preferences DataStore i Proto DataStore — oba demonstrują odczyt, zapis i reaktywne obserwowanie danych.

Preferences DataStore: odczyt i zapis ustawień

W tym przykładzie Preferences DataStore przechowuje trzy ustawienia: ciemny motyw, nazwę użytkownika i liczbę uruchomień. Odczyt wykonywany jest przez rozszerzenie .data, które zwraca Flow. Zapis — przez suspend-funkcję .edit, gwarantującą atomowość zmian.

kotlin
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: schemat i użycie

Proto DataStore wymaga zdefiniowania pliku .proto. Po kompilacji tworzona jest klasa UserSettings, używana do odczytu i zapisu. Migracje wersji schematu są opisywane w tym samym pliku .proto i stosowane automatycznie.

kotlin
// user_preferences.proto
syntax = "proto3";

message UserPreferences {
    string display_name = 1;
    int32 notification_count = 2;
    bool notifications_enabled = 3;
}

// Odczyt z DataStore
val userPreferencesFlow: Flow<UserPreferences> =
    protoDataStore.data

// Zapis nowych wartości
suspend fun updateDisplayName(name: String) {
    protoDataStore.updateData { prefs ->
        prefs.toBuilder()
            .setDisplayName(name)
            .build()
    }
}

Reaktywne obserwowanie zmian

DataStore integruje się z architekturą MVVM przez ViewModel. Flow z DataStore jest zbierany przez .stateIn i używany w UI. Przy każdej zmianie danych UI aktualizuje się automatycznie — nie są potrzebne ręczne aktualizacje ani LiveData.

kotlin
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()
            )
}

Często zadawane pytania

Czym DataStore jest lepszy od SharedPreferences?

DataStore działa asynchronicznie (nie blokuje wątku UI), obsługuje współbieżny dostęp przez transakcje i pozwala reaktywnie subskrybować zmiany przez Flow. SharedPreferences — synchroniczne API z ryzykiem ANR przy dużych woluminach danych i bez wbudowanego wsparcia dla reaktywności.

Czy można używać DataStore z Javą?

DataStore jest napisany w Kotlin i wymaga Kotlin Coroutines. Używanie go z Javy jest możliwe, ale niewygodne: trzeba tworzyć nakładki z CompletableFuture lub ręcznie zarządzać coroutine. Dla projektów Java Google zaleca pozostanie przy SharedPreferences lub dodanie Kotlin do modułu.

Czy DataStore nadaje się do przechowywania dużych woluminów danych?

DataStore ładuje cały plik do pamięci przy odczycie, więc nie nadaje się do przechowywania list lub dużych obiektów. W takich scenariuszach używaj Room lub SQLite. DataStore jest zoptymalizowany do ustawień i małych strukturyzowanych danych — do setek kilobajtów.

Jak obsłużyć błąd uszkodzonego pliku DataStore?

Przy tworzeniu DataStore można przekazać corruptionHandler — funkcję wywoływaną przy uszkodzeniu pliku. Domyślnie DataStore zgłasza wyjątek CorruptionException. W corruptionHandler można zwrócić puste dane, po czym DataStore nadpisze plik poprawnym stanem.

Czy Proto DataStore wymaga obowiązkowego pliku .proto?

Tak, Proto DataStore wymaga zdefiniowania schematu w pliku .proto i podłączenia pluginu protobuf-gradle-plugin. Jeśli projekt jest mały, a dane proste, łatwiej użyć Preferences DataStore — nie wymaga dodatkowej konfiguracji budowania.

Podsumowanie

  • DataStore — nowoczesny zamiennik SharedPreferences działający z Kotlin Coroutines i Flow
  • Preferences DataStore — prosty klucz-wartość bez schematu, odpowiedni do ustawień
  • Proto DataStore — typowane repozytorium ze schematem protobuf i auto-migracją
  • Migracja z SharedPreferences jest wbudowana w DataStore przez SharedPreferencesMigration
  • Bezpieczeństwo wątków — wszystkie operacje są asynchroniczne, blokowanie UI wykluczone
  • Reaktywność — Flow powiadamia subskrybentów przy każdej zmianie danych
  • Zalecenie — używaj DataStore we wszystkich nowych projektach Android, migruj istniejące przy pracy z ustawieniami

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również