android mvi接口设计1

复制代码
为什么这么设计?
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 打好地基

相关推荐
火柴就是我6 小时前
Flutter 项目本地 Maven 引入 AAR 找不到?一次 Gradle 路径问题排查
android
火柴就是我6 小时前
Android 打包报错 25.0.3
android·前端
码上有光6 小时前
Linux进程通信——共享内存、消息队列和信号量
android·linux·运维·共享内存·通信
Android打工仔10 小时前
Kotlin 协程源码解析:DispatchedContinuation 里的 Dispatcher 从哪里来?
android·kotlin
打工仔折腾 AI10 小时前
从BPE到SentencePiece:Transformer分词原理与Python实战对比
android·人工智能·python·深度学习·langchain·transformer·ai agent 实战
Dovis(誓平步青云)11 小时前
几个方案来回选不定?做一个随时切换的候选推荐页
android·java·服务器·开发语言·javascript·数据库·智能化
恋猫de小郭15 小时前
KMP 又改了编译流程, Separate Compilation 禁止了 `commonMain` 的依赖穿透
android·前端·flutter
执明wa15 小时前
Android RecyclerView 实战:点击频道栏切换对应内容
android·开发语言·windows·microsoft·android studio
新鲜势力呀15 小时前
PHP 定时数据同步实战:从第三方接口超时到异步同步 + 增量更新架构优化全过程
android
星间都市山脉17 小时前
RecoveryUI 圆角屏安全边距配置及代码调用链
android·java·linux·windows·ubuntu