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
    }
}

我为此做了一个解决方法。

  1. 您将属性的先前值存储在变量中
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
        }
  1. 每次要更新数据时,首先将新值与前一个值进行比较。
  2. 并且如果数据相同,在推送你真正的新数据之前,你推送一些保证与新数据不相同的假数据。 这也可以通过在配置数据中引入一些额外的变量来实现 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 有朝一日能为我们提供更好的方法。