Kotlin 2.3 引入了explicit backing fields,允许你用单个属性替换经典的 _state + state 模板代码,你的 ViewModel 将变得前所未有的清爽。
现状:被“下划线”统治的模板代码
多年来,为了确保封装性,Android 开发者一直遵循着一种“标准仪式”:创建一个私有的 MutableStateFlow 用于内部修改,再暴露一个公有的 StateFlow 供外部读取。
“旧”方案(繁琐的样板代码)
Kotlin
// 内部使用的可变状态
private val _uiState = MutableStateFlow(UiState.Loading)
// 暴露给外部的只读视图
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
虽然这种做法行之有效,但它不仅让 IDE 的自动补全充满了各种带下划线的变量,还为每一个 ViewModel 增加了大量的变量定义。
显式幕后字段(Explicit Backing Fields)
在 Kotlin 2.3 中,你现在可以在单个属性内部定义一个 field 块。这让你可以为属性定义特定的内部存储类型,同时对外界仅暴露只读的 API。
“新”方案(优雅的单属性)
Kotlin
class UserProfileViewModel : ViewModel() {
// 同一个属性名,两种不同的类型
val uiState: StateFlow<ProfileState>
field = MutableStateFlow(ProfileState.Loading)
fun updateUsername(newName: String) {
// 在类内部,'field' 让我们直接拥有可变的访问权限
field.value = ProfileState.Success(username = newName)
}
}
⚠️ 工程实践中的现实问题
在你打算删掉项目中所有的下划线变量之前,有几个关键的工程细节需要你知晓:
- 只读 vs 不可变:
StateFlow或List只是只读视图,而非绝对不可变。虽然外部 API 没有提供修改工具,但底层的explicit backing fields依然是可变的。 - 仅限简单初始化: explicit backing fields最适合简单的赋值。如果你的状态需要复杂的链式操作(如
.stateIn())或者基于构造参数的复杂条件逻辑,传统的双属性方案依然更具灵活性。 - 严格的实验性阶段: 该特性目前隐藏在
-Xexplicit-backing-fields编译器参数之后。在当前版本的 Android Studio 中,语法高亮和重构支持可能还不够稳定。 - 运行时安全缺口: 传统的
_state.asStateFlow()模式会创建一个包装层,防止外部在运行时将其强转回MutableStateFlow。explicit backing fields目前在底层并不天生具备这种“防强转”机制。
总结
explicit backing fields让代码更加干净、紧凑。虽然目前它还处于实验阶段,但它代表了 Kotlin 消除样板代码、提升开发者幸福感的明确方向。