上一章節介紹了怎麼長久的保留規整資料,但是我們除了這些todo之外,還要記錄使用者的習慣或設定等訊息,這時候就需要DataStore了
DataStore有兩種,一個是Preference datastore,另一個是Proto datastore,本篇會聚焦在Preference上,但也會稍微解釋不同之處與原因
這是本篇會使用的設定儲存方式,它的本質就是map,就是key對value的關聯
這是另一種方式,但我對他不太了解
但因為這邊是新手入門且非常小型的專案,我沒有考慮什麼scalable的問題,更沒有什麼code好不好懂的問題,先做出一個明確能跑的應用,之後就能開始鑽研各個細節
既然要交紹Preference Datastore,就先了解他的架構
implementation("androidx.datastore:datastore-preferences:1.1.7")
只要這個就好了
先另外建一個檔案(我選擇settingpage旁邊)
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
Kotlin語法糖:在Class後面用.xxx接上可以為class補充函數或屬性,你可以在非class的scope複寫新增非final宣告的函數或類別,在此dataStore只是一個屬性名稱
在一個檔案裡面只能有一個生效的datastore
宣告一個屬性(Android官方程式)
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
我們會使用到的是EXAMPLE_COUNTER,但是expample_counter才是真正的識別方式,如果更改了name屬性,就無法對到原本的屬性設定
val exampleCounterFlow: Flow<Int> = context.dataStore.data
.map { preferences ->
// No type safety.
preferences[EXAMPLE_COUNTER] ?: 0
}
因為這個datastore沒有null safety,所以要自己排除
suspend fun incrementCounter() {
// context = LocalContext.Current
context.dataStore.edit { settings ->
val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
settings[EXAMPLE_COUNTER] = currentCounterValue + 1
}
}
因為他是suspend fun跟database一樣,所以一樣要開Coroutine
我試過了一些宣告與使用方式,雖然無法大量減少使用的複雜度,但還是有一點心得的
如果要儲存map、list之類的也是可以,但不建議,因為需要轉換成字串在轉換回來
使用object讓資料聚集,方便查找
sealed class AppPreference<T>(
val key: Preferences.Key<T>,
val defaultValue: T
) {
open class Str(key: Preferences.Key<String>) : AppPreference<String>(key, "")
open class Bool(key: Preferences.Key<Boolean>) : AppPreference<Boolean>(key, false)
open class IntPref(key: Preferences.Key<Int>) : AppPreference<Int>(key, 0)
object FINISHED_DESC : AppPreference.Bool(booleanPreferencesKey("finished_desc"))
}
我大概解釋一下,他是一個可以存key和default value的class,之後有幾個初始化class快速初始化
使用泛型,讓基本的屬性查找不用寫很長一段的Conetext和map,可以簡化成getValueFlow().collectAsState()
object DataStoreManager {
lateinit var dataStore: DataStore<Preferences>
fun <T> getValueFlow(key: Preferences.Key<T>, default: T): Flow<T> =
dataStore.data.map { it[key] ?: default }
suspend fun <T> setValue(key: Preferences.Key<T>, value: T) {
dataStore.edit { it[key] = value }
}
}
基於它的特性,如果這個鍵值之前沒有值的話就是null,所以可以在object建立的時候對每一個鍵值掃描null,然後賦值,也是因為如此,我才認為這個方式很不scalable
但是如果用我的那個方式,只需要將所有的鍵值建成一個list,然後使用default value初始化就可以
這邊多加一個沒用的屬性展示
val prefKeyList = listOf<AppPreference<*>>(
AppPreference.FINISHED_DESC,
AppPreference.NOTHING_INT
)
fun init() {
CoroutineScope(Dispatchers.IO).launch {
for (pref in prefKeyList) {
dataStore.edit { prefs ->
@Suppress("UNCHECKED_CAST")
val p = pref as AppPreference<Any>
prefs[p.key] = prefs[p.key] ?: p.defaultValue
}
}
}
}
Kotlin語法:由於函數不能確認pref是不是有型別綁定(int key to int value),所以要cast一下,讓編譯器閉嘴