为什么这么设计?
interface SimpleUiState<T> : UiState {
val loadState: LoadState
val data: T?
}
先看这个接口在"表达什么"
kotlin
interface SimpleUiState<T> : UiState {
val loadState: LoadState
val data: T?
}
它在 强制规定:
一个 UI 状态 =「加载状态」+「业务数据」
这不是随便写的,这是 刻意的状态建模(State Modeling)。
一、为什么要把 loadState 和 data 拆开?(核心原因)
❌ 初学者最常见的写法(问题很多)
kotlin
sealed class UiState {
object Loading : UiState()
data class Success(val data: T) : UiState()
data class Error(val msg: String) : UiState()
}
看起来很完美,但在真实 App 里会出大问题。
❗真实 UI 场景是「叠加态」,不是「互斥态」
比如这些情况 👇
| 场景 | Loading | Data | Error |
|---|---|---|---|
| 首次加载 | ✅ | ❌ | ❌ |
| 下拉刷新 | ✅ | ✅(旧数据) | ❌ |
| 加载失败(但保留旧数据) | ❌ | ✅ | ✅ |
| 重试中 | ✅ | ✅ | ❌ |
👉 Loading / Data / Error 并不是互斥的!
而 sealed class 的写法 强制它们互斥 ,这是模型错误。
二、SimpleUiState 解决了什么问题?
kotlin
val loadState: LoadState
val data: T?
1️⃣ UI 可以"同时"知道三件事
- 当前在不在加载?
- 有没有数据?
- 错误是什么?
kotlin
when (state.loadState) {
LoadState.Loading -> showLoading()
LoadState.Error -> showError()
LoadState.Idle -> hideLoading()
}
state.data?.let {
showContent(it)
}
加载状态 ≠ 数据是否存在
2️⃣ 支持「保留旧数据 + 新加载」
kotlin
// 下拉刷新
loadState = Loading
data = oldData
UI 效果:
✅ 列表还在
✅ 顶部 loading 转
❌ 不闪屏
👉 这就是大厂最常用的体验设计
3️⃣ 强制 ViewModel 维护完整状态
这个接口 不是给 UI 用的,是约束 ViewModel 的
kotlin
class XxxViewModel {
val uiState: StateFlow<SimpleUiState<List<Item>>>
}
ViewModel 不能只发 data 或只发 loading
而是必须发一个 完整的状态快照
👉 防止"状态分裂"
三、为什么 data 要是 T?
这是一个非常关键的设计点 👇
场景拆解
| 场景 | data |
|---|---|
| 首次加载 | null |
| 加载成功 | 非 null |
| 加载失败 | null 或旧数据 |
| 刷新失败 | 旧数据 |
👉 data 的存在与否 ≠ 当前是否成功
❌ 如果不用 T?
kotlin
val data: T
那你就会被迫:
- 初始化一个假数据
- 或用 EmptyObject
- 或用魔法值
👉 UI 和 ViewModel 都会变脏
四、LoadState 为什么要单独存在?
通常 LoadState 会是:
kotlin
sealed class LoadState {
object Idle : LoadState()
object Loading : LoadState()
data class Error(val throwable: Throwable) : LoadState()
}
这样设计的好处:
-
错误 不是 UI State 本身
-
错误只是「加载过程中的一个状态」
-
UI 可以决定:
- 是否弹 toast
- 是否显示 error view
- 是否静默失败
五、为什么用 interface,而不是 data class?
kotlin
interface SimpleUiState<T> : UiState
这是为 扩展性 设计的
你可以这样👇
kotlin
data class MusicUiState(
override val loadState: LoadState,
override val data: List<Music>?,
val playingId: String?,
val isShuffle: Boolean
) : SimpleUiState<List<Music>>
👉 公共规范 + 业务扩展
六、总结一句话(设计哲学)
UI State 不是「结果」,而是「过程 + 当前数据快照」
SimpleUiState<T> 的设计本质是:
- ✅ 把「加载过程」和「业务数据」解耦
- ✅ 支持真实世界的「并行状态」
- ✅ 防止 ViewModel 状态失控
- ✅ 为 Compose / StateFlow / MVI 打好地基