Compose中使用StateFlow更新对象属性时UI未即时刷新问题
遵循Google最新教程在新项目中引入Compose/ViewModel/State时遇到问题:使用ViewModel中的方法将对象从null更新为新实例时,UI会正常更新;但仅更新该对象的一个参数时,修改无法即时显示。具体表现为:修改参数后调用同样的update方法,日志显示新值已传递,但只有取消选中再重新选中该Step时,新数据才会显示。Step是data class,而类似的更新布尔值(非对象)的方法可以正常工作。
ViewModel代码
data class AppUiState( val selectedStep: Step? = null ) class AppViewModel() : ViewModel(){ private val _uiState = MutableStateFlow(AppUiState()) val uiState: StateFlow<AppUiState> = _uiState.asStateFlow() fun updateSelectedStep(newStep: Step){ _uiState.update { currentState -> currentState.copy(selectedStep = newStep) } // 也尝试过 _uiState.value = _uiState.value.copy(selectedStep = newStep) } }
Composable代码
fun CardDetail( appViewModel: AppViewModel ) { val appUiState by appViewModel.uiState.collectAsState() Column( Modifier .fillMaxSize() .padding(horizontal = 16.dp, vertical = 8.dp), ) { Text( text = appUiState.selectedStep!!.status.toString(), ) OutlinedButton( onClick = { selectedStep!!.status = 16 appViewModel.updateSelectedStep(selectedStep) }, ) { Text( stringResource(R.string.it_starts), ) } } }
问题根源
StateFlow(以及Compose的状态更新机制)是基于值的相等性来判断是否需要发射新状态、触发UI重组的。
你现在的操作是直接修改了selectedStep对象的status属性,但这个selectedStep的引用并没有变——传给updateSelectedStep的还是原来的对象实例。AppUiState.copy()虽然生成了新的AppUiState实例,但它内部的selectedStep指向的还是同一个Step对象。由于data class自动生成的equals方法会比较所有属性的相等性,当selectedStep引用相同时,新、旧AppUiState会被判定为相等,StateFlow不会发射新状态,自然不会触发UI重组。
而更新布尔值(基础类型)时,修改后会生成新的值,AppUiState.copy()后的新实例和旧实例会被判定为不相等,StateFlow会发射新状态,UI正常更新。
修复方案
因为Step是data class,它自带copy()方法可以生成新实例,你需要在修改status时创建Step的新实例,而非直接修改原对象的属性:
修改Composable中的按钮点击逻辑:
OutlinedButton( onClick = { val updatedStep = appUiState.selectedStep!!.copy(status = 16) appViewModel.updateSelectedStep(updatedStep) }, ) { Text( stringResource(R.string.it_starts), ) }
这样,传给updateSelectedStep的是全新的Step实例,AppUiState.copy()生成的新AppUiState实例和旧实例会被判定为不相等,StateFlow会发射新状态,Composable收集到新状态后就会触发UI重组,即时显示更新后的status值。
另外建议:避免在Composable中直接修改ViewModel持有的状态对象(比如selectedStep!!.status = 16这类操作),始终通过ViewModel提供的方法更新状态,保持状态的单一数据源,这是Jetpack架构的最佳实践。
内容的提问来源于stack exchange,提问作者Turvy

