Android StateFlow 集合覆盖 UI 更改

Android StateFlow collection overrides UI changes

假设有一个带有 SwitchEditText 控件的 FilterActivity。后者已禁用输入但可点击。单击它会启动 TypeActivity 以从中选择一个类型值,然后将类型名称填充到 EditText 中。

有一个 StateFlow<FilterUiState>

的 FilterViewModel
data class FilterUiState(
    var status: Boolean = false,
    var type: String = "sometype"
)

在onCreate中由activity这样收集

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect {
            binding.run {
                statusSwitch.isChecked = it.status
                typeEditText.text = it.type
            }
        }
    }           
}

问题是,如果用户更改 statusSwitch 状态,然后单击 typeEditText 来选择一个类型,然后在 return 后恢复 FilterActivity,这会触发 uiState.collect 并因此重置statusSwitch 检查状态回到初始值。

防止 UI 更改被 StateFlow 收集覆盖的正确方法是什么?

您需要使用您希望它保持的最新状态实际更新状态流,然后它会在收集时正确恢复最新状态。

此外,不要为此使用可变 class。将可变 classes 与 StateFlows 混合使用很容易出错。如果您改变它已经持有的实例,StateFlow 无法检测到发生了变化。

data class FilterUiState(
    val status: Boolean = false,
    val type: String = "sometype"
)
// in ViewModel class:

private val mutableUiState = with(PreferenceManager.getDefaultSharedPreferences(context)) {
    val status = //...get these from shared preferences
    val type = //...
    val initialState = FilterUiState(status, type)
    MutableStateFlow(initialState)
}
val uiState = mutableUiState.asStateFlow()

fun updateStatus(status: Boolean) {
    mutableUiState.value = mutableUiState.value.copy(status = status)
    // and update the SharedPreferences value
}

fun updateType(type: String) {
    mutableUiState.value = mutableUiState.value.copy(type = type)
    // and update the SharedPreferences value
}

并在这些值发生变化时从 Activity 调用这两个 update 函数。您可以向复选框添加一个点击侦听器,以针对“状态”和 EditText 执行此操作,因为您已禁用输入,所以您可以调用 updateType() 函数,而不是在从另一个返回时直接修改其文本 Activity。当您更新视图模型中的状态时,您现有的收集器会为您更新小部件中的文本。