Android 首选项数据存储流不发出相同的值
Android Preferences DataStore Flow Doesn't Emit Same Value
只是测试 Preferences DataStore 并发现所提供的 Flow
输出不会发出相同的值,我的设置如下:
DataStore 实用程序 Class:
object DataStore {
private val Context.settings by preferencesDataStore("settings")
suspend fun saveBoolean(context: Context, keyResId: Int, value: Boolean) {
val key = booleanPreferencesKey(context.getString(keyResId))
context.settings.edit {
it[key] = value
}
}
fun getBooleanFlow(context: Context, keyResId: Int, defaultValueResId: Int): Flow<Boolean> {
val key = booleanPreferencesKey(context.getString(keyResId))
val defaultValue = context.resources.getBoolean(defaultValueResId)
return context.settings.data.map {
it[key] ?: defaultValue
}
}
}
ViewModel Class:
class FirstViewModel(application: Application) : AndroidViewModel(application) {
private val uiScope = viewModelScope
val isUpdateAvailable = DataStore.getBooleanFlow(
getApplication(), R.string.is_update_available_key, R.bool.is_update_available_default
)
fun updateIsUpdateAvailable() = uiScope.launch {
DataStore.saveBoolean(getApplication(), R.string.is_update_available_key, true) //<- always set to true
}
}
片段Class:
class FirstFragment : Fragment() {
private lateinit var binding: FragmentFirstBinding
private lateinit var viewModel: FirstViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_first, container, false)
viewModel = ViewModelProvider(this).get(FirstViewModel::class.java)
lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.isUpdateAvailable.collect {
Log.v("xxx", "isUpdateAvailable: $it")
}
}
}
binding.saveButton.setOnClickListener {
viewModel.updateIsUpdateAvailable()
}
return binding.root
}
}
因为我每次都保存 true
,而 Log
只显示一次,这意味着 Flow
不会发出相同的值。我对么?这是故意的行为吗?
对,context.settings.data
流不会发出相同的值。我还没有找到任何文档来证实这一点,但是深入研究 DataStore 库的来源表明,如果当前值等于新值,则不会发生发射。一个更新值的函数源码:
private val downstreamFlow = MutableStateFlow(...)
private suspend fun transformAndWrite(
transform: suspend (t: T) -> T,
callerContext: CoroutineContext
): T {
val curDataAndHash = downstreamFlow.value as Data<T>
curDataAndHash.checkHashCode()
val curData = curDataAndHash.value
val newData = withContext(callerContext) { transform(curData) }
curDataAndHash.checkHashCode()
// here comparison is happening
return if (curData == newData) {
curData
} else {
// if curData and newData are not equal save and emit newData
writeData(newData)
downstreamFlow.value = Data(newData, newData.hashCode())
newData
}
}
我为此做了一个解决方法。
- 您将属性的先前值存储在变量中
private val dataStore = context.dataStore
private var previousConfig: AppConfig = AppConfig.default() //Any default/emptyvalue
val preferencesFlow = dataStore.data
.catch { exception ->
Log.e(TAG, "Error reading preferences", exception)
emit(emptyPreferences())
}
.map { preferences ->
val result = preferences[CONFIG]?.let {
moshiAdapter.fromJson(it) ?: AppConfig.default()
} ?: AppConfig.default()
previousConfig = result //here you update your previous value
return@map result
}
- 每次要更新数据时,首先将新值与前一个值进行比较。
- 并且如果数据相同,在推送你真正的新数据之前,你推送一些保证与新数据不相同的假数据。
这也可以通过在配置数据中引入一些额外的变量来实现 class。在比较数据 class 实例时,此变量将专用于使 equal() return 为假。然后你可以推送你的新数据(见下面的代码)。
suspend fun updateConfig(appConfig: AppConfig) {
if (appConfig == previousConfig) {
dataStore
.edit { preferences -> // pushing fake data
preferences[PreferencesKeys.CONFIG] = moshiAdapter
.toJson(appConfig.copy(equalityIndicator++))
}
}
dataStore
.edit { preferences -> //now pushing the real valid new data
preferences[PreferencesKeys.CONFIG] = moshiAdapter.toJson(appConfig)
}
}
这是一个丑陋的解决方法,但它确实有效。我希望 Google 有朝一日能为我们提供更好的方法。
只是测试 Preferences DataStore 并发现所提供的 Flow
输出不会发出相同的值,我的设置如下:
DataStore 实用程序 Class:
object DataStore {
private val Context.settings by preferencesDataStore("settings")
suspend fun saveBoolean(context: Context, keyResId: Int, value: Boolean) {
val key = booleanPreferencesKey(context.getString(keyResId))
context.settings.edit {
it[key] = value
}
}
fun getBooleanFlow(context: Context, keyResId: Int, defaultValueResId: Int): Flow<Boolean> {
val key = booleanPreferencesKey(context.getString(keyResId))
val defaultValue = context.resources.getBoolean(defaultValueResId)
return context.settings.data.map {
it[key] ?: defaultValue
}
}
}
ViewModel Class:
class FirstViewModel(application: Application) : AndroidViewModel(application) {
private val uiScope = viewModelScope
val isUpdateAvailable = DataStore.getBooleanFlow(
getApplication(), R.string.is_update_available_key, R.bool.is_update_available_default
)
fun updateIsUpdateAvailable() = uiScope.launch {
DataStore.saveBoolean(getApplication(), R.string.is_update_available_key, true) //<- always set to true
}
}
片段Class:
class FirstFragment : Fragment() {
private lateinit var binding: FragmentFirstBinding
private lateinit var viewModel: FirstViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_first, container, false)
viewModel = ViewModelProvider(this).get(FirstViewModel::class.java)
lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.isUpdateAvailable.collect {
Log.v("xxx", "isUpdateAvailable: $it")
}
}
}
binding.saveButton.setOnClickListener {
viewModel.updateIsUpdateAvailable()
}
return binding.root
}
}
因为我每次都保存 true
,而 Log
只显示一次,这意味着 Flow
不会发出相同的值。我对么?这是故意的行为吗?
对,context.settings.data
流不会发出相同的值。我还没有找到任何文档来证实这一点,但是深入研究 DataStore 库的来源表明,如果当前值等于新值,则不会发生发射。一个更新值的函数源码:
private val downstreamFlow = MutableStateFlow(...)
private suspend fun transformAndWrite(
transform: suspend (t: T) -> T,
callerContext: CoroutineContext
): T {
val curDataAndHash = downstreamFlow.value as Data<T>
curDataAndHash.checkHashCode()
val curData = curDataAndHash.value
val newData = withContext(callerContext) { transform(curData) }
curDataAndHash.checkHashCode()
// here comparison is happening
return if (curData == newData) {
curData
} else {
// if curData and newData are not equal save and emit newData
writeData(newData)
downstreamFlow.value = Data(newData, newData.hashCode())
newData
}
}
我为此做了一个解决方法。
- 您将属性的先前值存储在变量中
private val dataStore = context.dataStore
private var previousConfig: AppConfig = AppConfig.default() //Any default/emptyvalue
val preferencesFlow = dataStore.data
.catch { exception ->
Log.e(TAG, "Error reading preferences", exception)
emit(emptyPreferences())
}
.map { preferences ->
val result = preferences[CONFIG]?.let {
moshiAdapter.fromJson(it) ?: AppConfig.default()
} ?: AppConfig.default()
previousConfig = result //here you update your previous value
return@map result
}
- 每次要更新数据时,首先将新值与前一个值进行比较。
- 并且如果数据相同,在推送你真正的新数据之前,你推送一些保证与新数据不相同的假数据。 这也可以通过在配置数据中引入一些额外的变量来实现 class。在比较数据 class 实例时,此变量将专用于使 equal() return 为假。然后你可以推送你的新数据(见下面的代码)。
suspend fun updateConfig(appConfig: AppConfig) {
if (appConfig == previousConfig) {
dataStore
.edit { preferences -> // pushing fake data
preferences[PreferencesKeys.CONFIG] = moshiAdapter
.toJson(appConfig.copy(equalityIndicator++))
}
}
dataStore
.edit { preferences -> //now pushing the real valid new data
preferences[PreferencesKeys.CONFIG] = moshiAdapter.toJson(appConfig)
}
}
这是一个丑陋的解决方法,但它确实有效。我希望 Google 有朝一日能为我们提供更好的方法。