渐进式编程课
KMP 目录
第 14 课2026.07.30

Android 和 iOS 页面通常都要处理加载中、成功和失败。如果两端各自编写一套状态转换,行为容易不一致。可以在 commonMain 中建立状态持有者,把数据层结果转换为统一状态,再让平台 UI 负责渲染。

第 14 课:构建共享状态持有者

  • 日期:2026-07-30
  • 课程序号:14
  • 知识点:把 Repository 结果转换为可观察的共享 UI 状态

用途

Android 和 iOS 页面通常都要处理加载中、成功和失败。如果两端各自编写一套状态转换,行为容易不一致。可以在 commonMain 中建立状态持有者,把数据层结果转换为统一状态,再让平台 UI 负责渲染。

核心概念

  • 使用密封类型列出所有合法页面状态。
  • 使用私有 MutableStateFlow 更新状态,对外暴露只读 StateFlow。
  • 状态持有者调用 Repository,并把 DataResult 映射为 UI 状态。
  • UI 不再额外维护 isLoading、data、error 三份可能冲突的变量。
  • 协程由平台生命周期启动;共享状态持有者不创建全局作用域。

示例代码

先定义完整且互斥的状态:

kotlin
sealed interface UserUiState {
    data object Idle : UserUiState
    data object Loading : UserUiState
    data class Content(val user: User) : UserUiState
    data class Error(val error: DataError) : UserUiState
}

状态持有者只依赖 Repository:

kotlin
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

class UserStore(
    private val repository: UserRepository,
) {
    private val _state =
        MutableStateFlow<UserUiState>(UserUiState.Idle)

    val state: StateFlow<UserUiState> = _state.asStateFlow()

    suspend fun load(id: Long) {
        _state.value = UserUiState.Loading

        _state.value = when (val result = repository.loadUser(id)) {
            is DataResult.Success -> {
                UserUiState.Content(result.value)
            }

            is DataResult.Failure -> {
                UserUiState.Error(result.error)
            }
        }
    }
}

平台调用层在自己的生命周期协程中执行加载:

kotlin
lifecycleScope.launch {
    userStore.load(id = 1L)
}

UI 收集 state,并穷尽渲染:

kotlin
when (val state = userStore.state.value) {
    UserUiState.Idle -> Unit
    UserUiState.Loading -> showLoading()
    is UserUiState.Content -> showUser(state.user)
    is UserUiState.Error -> showError(state.error)
}

真实项目还需要根据产品协议明确重复加载、取消和重试语义;不要靠额外 UI 状态锁自行猜测。

5 分钟练习

给 UserStore 增加 clear() 方法,将当前状态恢复为 UserUiState.Idle。

参考答案

kotlin
fun clear() {
    _state.value = UserUiState.Idle
}

只有明确的业务或页面事件调用 clear() 时才重置状态,不应在 UI 渲染过程中自动修改共享状态。

与上一课的联系

上一课在 Composition Root 中组装 Repository 和用例;本课增加可被平台 UI 消费的共享状态持有者,把“数据源 → Repository → DataResult → UiState → UI”连接成一条完整数据链路。