Compose中UiState属性更新:为Menu状态添加isButtonVisible
解决方案
1. 更新UiState密封类,添加isButtonVisible属性
修改Menu数据类,新增isButtonVisible参数并设置默认值,确保初始状态符合预期:
public sealed class UiState { public class Loading( @StringRes public val message: Int ) : UiState() public data class Menu( @StringRes public val errorMessage: Int? = null, public val isButtonVisible: Boolean = true // 新增属性,默认按钮可见 ) : UiState() public companion object { public val Initial: UiState = Menu() } }
2. 在ViewModel中跟踪最近的Menu状态
为了切换到Loading状态后返回Menu时保留isButtonVisible的状态,单独存储最近一次的Menu状态:
@HiltViewModel public class HomeViewModel @Inject internal constructor( private val appNavigator: AppNavigator, private val createBoardUseCase: Provider<CreateBoardUseCase>, private val generateSudokuUseCase: Provider<GenerateSudokuUseCase>, private val getLastGameUseCase: Provider<GetLastGameUseCase>, private val getCurrentYearUseCase: Provider<GetCurrentYearUseCase> ) : ViewModel() { private val _uiState = MutableStateFlow(UiState.Initial) public val uiState: StateFlow<UiState> get() = _uiState.asStateFlow() public val currentYear: String get() = getCurrentYearUseCase.get().invoke() // 存储最近一次的Menu状态,初始为默认值 private var lastMenuState: UiState.Menu = UiState.Initial as UiState.Menu init { // 监听uiState变化,自动更新lastMenuState viewModelScope.launch { uiState.collect { state -> if (state is UiState.Menu) { lastMenuState = state } } } }
3. 实现更新isButtonVisible的方法
使用MutableStateFlow.update结合data class的copy方法,仅在当前状态为Menu时修改属性:
// 示例:切换按钮可见性 public fun toggleButtonVisibility() { _uiState.update { currentState -> if (currentState is UiState.Menu) { currentState.copy(isButtonVisible = !currentState.isButtonVisible) } else { currentState // 非Menu状态时不做修改 } } }
4. 修改状态切换逻辑,保留isButtonVisible
从Loading切换回Menu时,基于lastMenuState创建新状态,而非直接实例化新的Menu对象:
public fun onStartClick() { viewModelScope.launch(Dispatchers.IO) { _uiState.update { Loading(message = R.string.preparing_game_please_wait) } val board = try { generateSudokuUseCase.get().invoke() } catch (ex: SudokuNotGenerated) { // 创建带错误信息的Menu状态,会被自动记录到lastMenuState _uiState.update { UiState.Menu(errorMessage = R.string.err_generate_sudoku) } return@launch } val insertedBoardUid = createBoardUseCase.get().invoke(board) navigateToGame(insertedBoardUid) // 基于lastMenuState创建新状态,保留isButtonVisible,清除错误信息 _uiState.update { lastMenuState.copy(errorMessage = null) } } }
完整修改后的ViewModel代码
@HiltViewModel public class HomeViewModel @Inject internal constructor( private val appNavigator: AppNavigator, private val createBoardUseCase: Provider<CreateBoardUseCase>, private val generateSudokuUseCase: Provider<GenerateSudokuUseCase>, private val getLastGameUseCase: Provider<GetLastGameUseCase>, private val getCurrentYearUseCase: Provider<GetCurrentYearUseCase> ) : ViewModel() { private val _uiState = MutableStateFlow(UiState.Initial) public val uiState: StateFlow<UiState> get() = _uiState.asStateFlow() public val currentYear: String get() = getCurrentYearUseCase.get().invoke() private var lastMenuState: UiState.Menu = UiState.Initial as UiState.Menu init { viewModelScope.launch { uiState.collect { state -> if (state is UiState.Menu) { lastMenuState = state } } } } public fun toggleButtonVisibility() { _uiState.update { currentState -> if (currentState is UiState.Menu) { currentState.copy(isButtonVisible = !currentState.isButtonVisible) } else { currentState } } } public fun onStartClick() { viewModelScope.launch(Dispatchers.IO) { _uiState.update { Loading(message = R.string.preparing_game_please_wait) } val board = try { generateSudokuUseCase.get().invoke() } catch (ex: SudokuNotGenerated) { _uiState.update { UiState.Menu(errorMessage = R.string.err_generate_sudoku) } return@launch } val insertedBoardUid = createBoardUseCase.get().invoke(board) navigateToGame(insertedBoardUid) _uiState.update { lastMenuState.copy(errorMessage = null) } } } public fun onContinueGameClick() { TODO("In Development") } public fun onSettingsClick() { TODO("In Development") } public fun onRecordsClick() { TODO("In Development") } private fun navigateToGame(boardUid: Long) { appNavigator.tryNavigateTo(Destination.GameScreen(boardUid = boardUid)) } }
关键逻辑说明
- 更新Menu属性:利用data class的
copy方法,在保留其他属性的前提下修改isButtonVisible,避免状态丢失。 - 保留状态:通过
lastMenuState存储最近的Menu状态,从Loading切换回Menu时复用该状态,确保isButtonVisible的值不会被重置。 - 自动同步:监听
uiState的变化自动更新lastMenuState,无需手动维护状态副本,减少出错概率。
内容的提问来源于stack exchange,提问作者Japskiddin
相关产品推荐
相关产品推荐

