You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 10:14:52