之前Content Provider,Room,DataStore一起弄,對於蒟蒻我來說步子邁得太大了,bug滿天飛(DataStore一直給我報錯說同時打開了多個DataStore,卻又找不到問題所在),遂不得不暫且拋下DataStore換回SharedPreference,後來才發現是被Hook ...
之前Content Provider,Room,DataStore一起弄,對於蒟蒻我來說步子邁得太大了,bug滿天飛(DataStore一直給我報錯說同時打開了多個DataStore,卻又找不到問題所在),遂不得不暫且拋下DataStore換回SharedPreference,後來才發現是被Hook應用在啟動的時候,LSPosed檢測了新Module導致同時Hook到了模塊本身,然而模塊里又沒忽略自己,導致兩個Content Resolver同時請求了兩次數據。
既然bug已經解決了,那大可以把SharedPreference給扔了,由於Project目前過於混亂,不僅所有業務邏輯居然都在ViewModel和Provider里,而且SharedPreference的調用也分別在MainViewModel和MainProvider里寫了兩次,極為Chulu,為了以後自己和項目不爆炸,不得不學習一下工具類的封裝,免得重覆邏輯飛得到處都是。
然後就開始寫DataStoreUtil,由於會被重覆使用,所以必須使其遵循單例模式,因為本蒟蒻對單例模式這些東西一無所知,連object關鍵字都不知道是幹嘛的,所以瞎學一氣後,使用了最為簡單的懶漢模式,直接用object定義一個單例,反正程式在啟動時本來就要初始化DataStore的,懶漢模式的缺點在此就不構成影響,雙重校驗鎖這種高級東西以後有需要再用吧。
由於Project其他部分的邏輯還不支持非同步,所以我這裡之暴露的同步操作的方法,以後支持非同步了後,只要把private刪掉就可以了。這回順便還學習了個泛型,這樣就可以用一個方法來處理不同的類型,不用像以前那樣,定義一堆類似getBooleanPreference
,getStringPreference
的方法了。
val Context.dataStore by preferencesDataStore(name = "settings")
object DataStoreUtil {
private lateinit var dataStore: DataStore<Preferences>
fun initialize(context: Context) { dataStore = context.dataStore }
fun <T> getPreference(key: String, defaultValue: T): T {
return runBlocking { asyncGetPreference(key, defaultValue).first() }
}
fun <T> setPreference(key: String, value: T) {
runBlocking { asyncSetPreference(key, value) }
}
@Suppress("UNCHECKED_CAST")
private fun <T> asyncGetPreference(key: String, defaultValue: T): Flow<T> {
return when (defaultValue) {
is String -> dataStore.data.map { it[stringPreferencesKey(key)] ?: defaultValue }
is Boolean -> dataStore.data.map { it[booleanPreferencesKey(key)] ?: defaultValue }
else -> throw IllegalArgumentException("Wrong value provided with invalid value type")
} as Flow<T>
}
private suspend fun <T> asyncSetPreference(key: String, value: T) {
when (value) {
is String -> dataStore.edit { it[stringPreferencesKey(key)] = value }
is Boolean -> dataStore.edit { it[booleanPreferencesKey(key)] = value }
else -> throw IllegalArgumentException("Wrong value provided with invalid value type")
}
}
}
使用例:
var dataStore: DataStoreUtil = DataStoreUtil.apply { initialize(context) }
val appTheme = dataStore.getPreference("theme", "light")
dataStore.setPreference("theme", "dark")
得益於Kotlin提供的內置拓展函數,原本需要兩行的的代碼被精簡到了一行,美麗了不少。
除了搓一個單例,也可以用Kotlin的委托功能,像使用普通變數一樣進行DataStore的存取操作。
class dataStoreVariable <T> (private val context: Context, private val key: String, private val defaultValue: T) : ReadWriteProperty<Any?, T> {
private val dataStore by lazy { context.dataStore }
@Suppress("UNCHECKED_CAST")
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return when (defaultValue) {
is String -> runBlocking { dataStore.data.map { it[stringPreferencesKey(key)] ?: defaultValue } }
is Boolean -> runBlocking { dataStore.data.map { it[booleanPreferencesKey(key)] ?: defaultValue } }
else -> throw IllegalArgumentException("Wrong value provided with invalid value type")
} as T
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
when (value) {
is String -> runBlocking { dataStore.edit { it[stringPreferencesKey(key)] = value } }
is Boolean -> runBlocking { dataStore.edit { it[booleanPreferencesKey(key)] = value } }
else -> throw IllegalArgumentException("Wrong value provided with invalid value type")
}
}
}
使用例:
var appTheme: String by dataStoreVariable(activity, "theme", "light")
appTheme = "dark"
看起來也非常不錯,不過這裡只寫了同步的存取操作,如果需要非同步只需要稍作修改即可。
當然,使用Kotlin的擴展功能也是可以實現的,就像給Context擴展dataStore一樣,可以給Context擴展對DataStore進行各種操作的方法:
@Suppress("UNCHECKED_CAST")
fun <T> Context.getDataStoreValue(key: String, defaultValue: T): T {
val dataStore = dataStore
return when (defaultValue) {
is String -> runBlocking { dataStore.data.map { it[stringPreferencesKey(key)] ?: defaultValue }.first() }
is Boolean -> runBlocking { dataStore.data.map { it[booleanPreferencesKey(key)] ?: defaultValue }.first() }
else -> throw IllegalArgumentException("Wrong value provided with invalid value type")
} as T
}
fun <T> Context.setDataStoreValue(key: String, value: T) {
val dataStore = dataStore
when (value) {
is String -> runBlocking { dataStore.edit { it[stringPreferencesKey(key)] = value } }
is Boolean -> runBlocking { dataStore.edit { it[booleanPreferencesKey(key)] = value } }
else -> throw IllegalArgumentException("Wrong value provided with invalid value type")
}
}
然後就只需要拿著手上有的context
,就可以獲取DataStore里的值了:
var appTheme = context.getDataStoreValue("theme", "light")
context.setDataStoreValue("theme", "dark")
雖然說,工具類是一種違反面向對象思想的東西,應該多利用kotlin的特性(委托、擴展),但自己還是太菜了,所以這回在Project里依然選擇了單例,希望以後能儘可能把後面這兩種實現給用起來,還有太多東西需要學了(逃)。
如需轉載請註明出處(或此出處),謝謝!