Android DataStore 數據存儲
在 Android 開發中,持久化簡單的輕量資料(如使用者設定、主題切換)過去大多使用 SharedPreferences。然而,SharedPreferences 存在許多架構上的缺陷,例如同步 API 可能導致主執行緒卡頓 (ANR)、缺乏錯誤處理機制以及不支援安全性型別。
Jetpack DataStore 是 Google 推薦的新一代資料儲存解決方案,它基於 Kotlin 協程 (Coroutines) 與 Flow 建構,完全是非同步、執行緒安全且具有交易原子性的。
SharedPreferences vs. DataStore
| 特性 | SharedPreferences | DataStore |
|---|---|---|
| 執行緒模型 | 同步 API (讀取可能導致 ANR) | 非同步 (基於協程 與 Flow) |
| 錯誤處理 | 無 (讀取失敗只會回傳預設值) | 有 (使用 Flow 的 catch 捕捉異常) |
| 型別安全 | 弱 (Key-Value) | 強 (Proto DataStore) |
| 資料一致性 | 弱 (無交易保證) | 強 (具原子性和交易保證) |
| 遷移支援 | 無 | 有 (內建 SharedPreferences 遷移) |
依賴配置
首先,在 build.gradle 中加入 Preferences DataStore 依賴:
dependencies {
implementation("androidx.datastore:datastore-preferences:1.1.1")
}
選擇 DataStore 類型
DataStore 提供兩種不同的實作:
- Preferences DataStore:與 SharedPreferences 類似,使用 Key 儲存資料,不需要事先定義結構 (Schema)。適合大多數簡單設定。
- Proto DataStore:儲存自定義的物件型態(使用 Protocol Buffers 定義),提供完整的型別安全。適合資料結構較複雜的場景。
本文重點介紹最常用的 Preferences DataStore。
建立與定義 DataStore
宣告實例
通常在 Context 上透過擴充屬性 (Delegate) 來建立 DataStore 實例。
// settings_preferences.kt
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
// 定義 Key
object SettingsKeys {
val THEME_MODE = booleanPreferencesKey("theme_mode")
val USER_NAME = stringPreferencesKey("user_name")
}
讀取資料
DataStore 使用 Flow 來監聽資料變更。如果資料發生變化,Flow 會自動發射新的值。
val userThemeFlow: Flow<Boolean> = context.dataStore.data
.catch { exception ->
// 處理讀取時的 IO 異常
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}
.map { preferences ->
// 如果沒有值,則回傳預設值 (false = Light Mode)
preferences[SettingsKeys.THEME_MODE] ?: false
}
寫入資料
寫入操作必須在協程中執行。edit 函式確保了變更的原子性。
suspend fun toggleTheme(isDarkMode: Boolean) {
context.dataStore.edit { preferences ->
preferences[SettingsKeys.THEME_MODE] = isDarkMode
}
}
從 SharedPreferences 遷移資料
如果你的舊 App 已經使用了 SharedPreferences,DataStore 提供了一個非常簡單的遷移路徑。
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = "settings",
produceMigrations = { context ->
// 自動將舊的 SharedPreferences 資料搬移到 DataStore
listOf(SharedPreferencesMigration(context, "old_prefs_name"))
}
)
與 Hilt 及 ViewModel 整合
在現代架構中,我們會透過 Hilt 注入 DataStore,並在 ViewModel 中處理資料邏輯。
Hilt Module 提供 DataStore
@Module
@InstallIn(SingletonComponent::class)
object DataStoreModule {
@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Preferences> {
return context.dataStore
}
}
ViewModel 使用 Flow
@HiltViewModel
class SettingsViewModel @Inject constructor(
private val dataStore: DataStore<Preferences>
) : ViewModel() {
// 將 Flow 轉換為 StateFlow 供 UI 使用
val themeState: StateFlow<Boolean> = dataStore.data
.map { it[SettingsKeys.THEME_MODE] ?: false }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = false
)
fun updateTheme(isDarkMode: Boolean) {
viewModelScope.launch {
dataStore.edit { it[SettingsKeys.THEME_MODE] = isDarkMode }
}
}
}
透過切換到 DataStore,我們不僅解決了 SharedPreferences 引發的性能隱憂,更讓 App 的資料流符合響應式編程 (Reactive Programming) 的趨勢。