DataStore — компонента из библиотеке Jetpack намењена за чување малих количина података у Android апликацијама. За разлику од SharedPreferences, ради асинхроно и гарантује конзистентност података при конкурентном приступу. Према подацима Google, 2024, DataStore користи Kotlin Coroutines и Flow, што га чини безбедним за главну нит и погодним за реактивне архитектуре.
Главне тачке
DataStore — решење од Google-а за локално чување података у Android-у, представљено 2020. године као алтернатива SharedPreferences. Подржава два режима: Preferences DataStore (једноставни парови кључ-вредност) и Proto DataStore (типизирана шема заснована на Protocol Buffers).
Главна предност — потпуна асинхроност: све операције читања враћају Flow из Kotlin Coroutines, а упис се извршава у coroutine контексту. Ово елиминише блокирање главне нити, која је била типичан проблем SharedPreferences при раду са великим количинама података.
DataStore гарантује атомичност операција: конкурентни записи не доводе до губитка података захваљујући трансакционом моделу. Ако две компоненте истовремено мењају исту вредност, DataStore исправно обрађује конфликт кроз механизам compare-and-swap.
Према подацима Google I/O 2023, DataStore се користи у 40% нових пројеката на Android-у, и Google препоручује миграцију са SharedPreferences у свим апликацијама где је потребна стабилност чувања подешавања.
У основи DataStore лежи SingleProcessDataStore — имплементација која ради у оквиру једног процеса. Користи фајловско складиште са блокадама на нивоу фајла: при упису података фајл се блокира, што спречава оштећење при конкурентном приступу.
DataStore аутоматски обрађује грешке десеријализације: ако је фајл оштећен, враћа подразумевану вредност и преписује фајл. Ово понашање се подешава кроз corruptionHandler, који се може поставити при креирању DataStore.
SharedPreferences пати од три фундаментална проблема: синхроно читање са диска на главној нити, недостатак гаранција атомичности при конкурентним записима и немогућност реактивног праћења промена. DataStore решава сва три: Flow за посматрање, фајловска блокада за атомичност и асинхрони API за безбедност нити.
DataStore чува податке у фајловима на унутрашњој меморији уређаја. Preferences DataStore користи формат фајла сличан SharedPreferences, али са додатним метаподацима за проверу интегритета. Proto DataStore користи бинарни формат Protocol Buffers, што смањује величину фајла и убрзава серијализацију.
При читању података DataStore учитава цео фајл у меморију једнократно, након чега претплатници добијају тренутно стање кроз Flow. Промене се преносе свим активним претплатницима аутоматски — није потребна ручна регистрација listener-а, као у SharedPreferences.
Preferences DataStore користи уграђени механизам серијализације заснован на Map. Сваки унос је пар ниске и примитивног типа (Int, Boolean, Float, Long, String, Set). Подаци се чувају у XML фајлу, сличном SharedPreferences, али са атомичним уписом кроз фајловску блокаду.
Пример креирања Preferences DataStore: проширење preferencesDataStore на Context креира синглтон са именом фајла. При поновљеним позивима враћа се иста инстанца — ово елиминише дуплирање фајлова и забуну са различитим инстанцама складишта.
Proto DataStore захтева дефинисање шеме података кроз .proto фајл и компилацију помоћу protobuf додатка. Генерисана Java класа се користи као једина тачка улаза за сва поља — ово елиминише грешке у куцању кључева, типичне за SharedPreferences.
Шема Proto DataStore се дефинише једном и подржава додавање нових поља без губитка старих података. Ако се у новој верзији апликације дода поље са подразумеваном вредношћу, стари фајл ће бити исправно десеријализован — уназад компатибилност је уграђена у протокол.
Избор између Preferences DataStore и Proto DataStore зависи од сложености података и захтева за типизацијом. Обе варијанте су асинхроне и трансакционе, али се разликују по нивоу type-safety и перформансама серијализације.
| Карактеристика | Preferences DataStore | Proto DataStore |
|---|---|---|
| Типизација | Слаба (кључ-вредност) | Јака (генерисана класа) |
| Серијализација | XML (уграђена) | Protocol Buffers (protobuf) |
| Величина фајла | Велика (читак XML) | Мала (бинарни) |
| Сложеност | Ниска (без .proto) | Средња (потребан .proto) |
| Миграција шеме | Нема шеме | Аутоматска (proto) |
| Компатибилност | SharedPreferences (кроз миграцију) | Само Proto DataStore |
Preferences DataStore је погодан за једноставна подешавања: заставице за укључивање функција, низ токена за ауторизацију, број покретања апликације. Ако је података мало (до 10–15 кључева) и не захтевају строгу шему — Preferences DataStore даје минимални праг уласка без повезивања protobuf додатка.
Proto DataStore је оправдан када је структура података сложена или се може мењати између верзија апликације. На пример, подешавања профила корисника или конфигурација A/B тестова са 20+ поља. Protobuf даје јаку типизацију и аутоматске миграције, што елиминише грешке у извршавању због неусаглашености кључева.
Google пружа уграђени механизам миграције кроз класу SharedPreferencesMigration. Миграција се изводи једнократно при првом покретању након ажурирања апликације: DataStore чита податке из SharedPreferences, уписује их у свој формат и означава миграцију као завршену.
Миграција подржава прилагођене трансформације: ако се у SharedPreferences кључеви не поклапају са жељеним кључевима DataStore-а, може се дефинисати функција трансформације кроз SharedPreferencesMigration. Ово омогућава преименовање кључева и промену типова података у процесу миграције.
Први корак: додајте DataStore у build.gradle и креирајте инстанцу DataStore-а са миграцијом: SharedPreferencesMigration прима име SharedPreferences фајла и скуп кључева које треба пренети. Други корак: уклоните сав код који ради кроз SharedPreferences и замените га позивима DataStore-а. Трећи — тестирајте миграцију: при првом покретању подаци треба да се појаве у DataStore-у, а стари SharedPreferences фајл треба да престане да се користи.
val Context.dataStore by preferencesDataStore(
name = "settings",
produceMigrations = { context ->
listOf(
SharedPreferencesMigration(context, "old_prefs")
)
}
)
DataStore се лако интегрише у постојећи пројекат. Испод су дати практични примери за Preferences DataStore и Proto DataStore — оба демонстрирају читање, упис и реактивно праћење података.
У овом примеру Preferences DataStore чува три подешавања: тамну тему, име корисника и број покретања. Читање се врши кроз проширење .data, које враћа Flow. Упис — кроз suspend функцију .edit, која гарантује атомичност промена.
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 захтева дефинисање .proto фајла. Након компилације креира се класа UserSettings која се користи за читање и упис. Миграције верзија шеме се описују у истом .proto фајлу и примењују се аутоматски.
// user_preferences.proto
syntax = "proto3";
message UserPreferences {
string display_name = 1;
int32 notification_count = 2;
bool notifications_enabled = 3;
}
// Читање из DataStore
val userPreferencesFlow: Flow<UserPreferences> =
protoDataStore.data
// Упис нових вредности
suspend fun updateDisplayName(name: String) {
protoDataStore.updateData { prefs ->
prefs.toBuilder()
.setDisplayName(name)
.build()
}
}
DataStore се интегрише са MVVM архитектуром кроз ViewModel. Flow из DataStore-а се сакупља кроз .stateIn и користи се у UI-ју. При свакој промени података UI се ажурира аутоматски — нису потребна ручна ажурирања или 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()
)
}
Често постављана питања
DataStore ради асинхроно (не блокира UI нит), подржава конкурентни приступ кроз трансакције и омогућава реактивно праћење промена кроз Flow. SharedPreferences — синхрони API са ризиком од ANR при великим количинама података и без уграђене подршке за реактивност.
DataStore је написан у Kotlin-у и захтева Kotlin Coroutines. Коришћење из Java-е је могуће, али је незгодно: потребно је креирати омоте са CompletableFuture или ручно управљати корутинама. За Java пројекте Google препоручује да задржите SharedPreferences или додате Kotlin у модул.
DataStore учитава цео фајл у меморију при читању, стога није погодан за чување листи или великих објеката. За такве сценарије користите Room или SQLite. DataStore је оптимизован за подешавања и мале структуриране податке — до стотина килобајта.
При креирању DataStore-а може се проследити corruptionHandler — функција која се позива при оштећењу фајла. Подразумевано, DataStore баца изузетак CorruptionException. У corruptionHandler-у се могу вратити празни подаци, након чега DataStore преписује фајл исправним стањем.
Да, Proto DataStore захтева дефинисање шеме у .proto фајлу и повезивање додатка protobuf-gradle-plugin. Ако је пројекат мали и подаци једноставни, лакше је користити Preferences DataStore — не захтева додатну конфигурацију изградње.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође