Android MVI 中 setState(reduce: State.() -> State) 设计说明文档


Android MVI 中 setState(reduce: State.() -> State) 设计说明文档

1. 背景与设计目标

在 Android MVI(Model--View--Intent)架构中,State 是不可变的 ,UI 的任何变化都必须通过 State 的演进(reduction) 来完成。

本设计的核心目标是:

  • 保证 State 更新是原子性的
  • 确保 新 State 一定基于旧 State
  • 避免 UI 状态被意外重置
  • 让状态更新逻辑 集中、可预测、可追踪

2. 核心代码

kotlin 复制代码
protected fun setState(reduce: State.() -> State) {
    val newState = currentState.reduce()
    newState.toString().iLog(TAG)
    currentState = newState
    _uiState.value = currentState
}

3. 为什么 setState 要接收 Lambda?

3.1 reduce 不是 State,而是「状态演进规则」

kotlin 复制代码
State.() -> State

表示:

给我一个旧 State,我返回一个新 State

这不是一个值,而是一段 "如何从旧状态计算新状态的逻辑"


3.2 为什么不能设计成这样?(❌ 错误)

kotlin 复制代码
protected fun setState(state: State)
问题:
  1. ❌ 无法保证 state 基于最新的 currentState
  2. ❌ 状态可能在外部被提前计算,存在并发风险
  3. ❌ reducer 逻辑分散,破坏 MVI 的一致性
  4. ❌ 无法保证 State 的"演进关系"

3.3 正确模型(✅)

text 复制代码
NewState = reduce(OldState)

而不是:

text 复制代码
NewState = 某个外部算好的 State

4. Lambda 什么时候执行?(关键理解点)

调用代码:

kotlin 复制代码
setState {
    copy(loadState = LoadState.Loading)
}

执行时序:

  1. { copy(...) }

    👉 只是创建了一个 Lambda(函数值),不会执行

  2. 进入 setState

  3. 执行这一行:

kotlin 复制代码
val newState = currentState.reduce()

👉 此时 Lambda 才真正执行


核心规则(必须记住)

Lambda 在"被传递时不会执行",
只有在调用 () 时才会执行


5. 为什么 reducer 里几乎总是用 copy

5.1 State 的正确形态

kotlin 复制代码
data class XxxState(
    val loadState: LoadState = LoadState.Idle,
    val loadMoreState: LoadMoreState = LoadMoreState.Idle,
    val data: Data? = null
)

State 必须是:

  • data class
  • 不可变(val)
  • 可整体替换

5.2 copy 的作用

kotlin 复制代码
copy(loadState = LoadState.Loading)

含义是:

在旧 State 基础上,只修改指定字段,其余字段保持不变

等价于手写:

kotlin 复制代码
XxxState(
    loadState = LoadState.Loading,
    loadMoreState = this.loadMoreState,
    data = this.data
)

5.3 为什么不能 new 一个 State?(❌)

kotlin 复制代码
setState {
    XxxState(loadState = LoadState.Loading)
}
问题:
  1. ❌ 其他字段会被重置为默认值 / null
  2. ❌ 破坏 State 的连续性
  3. ❌ UI 会出现"莫名其妙回退 / 闪烁"的 bug
  4. ❌ reducer 语义被破坏(不再基于旧状态)

6. 为什么 State 常用「接口 + data class」?

kotlin 复制代码
interface UiState

interface XxxState : UiState {
    val loadState: LoadState
    val data: Data?
}

data class XxxStateImpl(
    override val loadState: LoadState = LoadState.Idle,
    override val data: Data? = null
) : XxxState

好处:

  • View 层依赖接口(解耦)
  • 内部实现可演进
  • reducer 中始终操作具体 data class(支持 copy

7. 正确使用方式(推荐写法)

✅ 标准用法

kotlin 复制代码
setState {
    copy(loadState = LoadState.Loading)
}
kotlin 复制代码
setState {
    copy(
        loadState = LoadState.LoadSuccess,
        data = result
    )
}

❌ 错误用法(禁止)

kotlin 复制代码
setState {
    XxxState(...)
}
kotlin 复制代码
setState(
    currentState.copy(...)
)

8. 注意事项(非常重要)

8.1 reducer 中不要有副作用

❌ 不要:

  • 发请求
  • 写数据库
  • 打 Toast
  • 启动协程

✅ reducer 只负责计算新 State


8.2 reducer 必须是"纯函数"

同样的输入 State,必须得到同样的输出 State。


8.3 不要在 reducer 外部修改 State

❌:

kotlin 复制代码
currentState = currentState.copy(...)

✅:

kotlin 复制代码
setState { copy(...) }

9. 一句话总结(可以放在文档最前面)

**setState 接收的不是 State,

而是"如何从旧 State 生成新 State 的规则"。

copy 是 MVI 中 State 演进的唯一正确方式。**


相关推荐
鸣弦artha2 小时前
Flutter框架跨平台鸿蒙开发——InheritedWidget基础使用-计数器案例
android·flutter·harmonyos
嵌入式-老费2 小时前
Android开发(开发板的三种操作系统)
android
凛_Lin~~3 小时前
安卓网络框架——OkHttp源码解析(基于3.14.x)
android·网络·okhttp
stevenzqzq3 小时前
android SharedFlow和Channel比较
android·channel·sharedflow
zhangphil4 小时前
Kotlin实现Glide/Coil图/视频加载框架(二)
android·kotlin
shughui4 小时前
APP、Web、H5、iOS与Android的区别及关系
android·前端·ios
千里马学框架4 小时前
敏感权限如何自动授权?pkms的permission部分常用命令汇总
android·车载系统·framework·perfetto·权限·系统开发·pkms
a2591748032-随心所记5 小时前
android14 google默认进程、apk、hal、以及service等
android
明天…ling5 小时前
四天学习笔记
android