1.MutableStateFlow: 可变的私有状态持有者
MutableStateFlow是一个特殊的热流 (Hot Flow),它持有单一的、可变的状态值。 在典型的 MVVM架构中,它通常被用在 ViewModel 中,并且被声明为私有的。 MutableStateFlow是状态的"写入者"或"管理者"。只有 ViewModel 内部的逻辑,例如响应用户操作、网络请求返回结果等,能够更新value 属性并改变状态。
特性:
•可变性 (Mutable): 它的值可以直接被修改。
•状态持有: 它总是有一个初始值,并且会向新的订阅者立即发送当前最新的值。 示例:
kotlin
class MyViewModel : ViewModel() {
// _uiState 是私有的,只能在 ViewModel 内部修改
private val _uiState = MutableStateFlow<MyUiState>(MyUiState.Loading)
// 公开一个只读的 StateFlow
val uiState: StateFlow<MyUiState> = _uiState.asStateFlow()
fun fetchData() {
// 模拟网络请求
viewModelScope.launch {
_uiState.value = MyUiState.Loading // 修改状态
delay(2000)
_uiState.value = MyUiState.Success("Data loaded!") // 再次修改状态
}
}
}
2.StateFlow: 只读的公共状态流
StateFlow是 MutableStateFlow的只读版本。 它通常通过在私有的 MutableStateFlow 上调用 .asStateFlow() 扩展函数来创建,并作为 ViewModel 的公共属性暴露给UI层 。
StateFlow 是状态的"只读暴露者"。UI 层可以订阅它来获取状态更新,但不能直接修改它。
特性:
•不可变性 : 你无法从外部更改 StateFlow 的值。
•安全性: 这种设计强制执行了单向数据流,UI 只能观察状态,而不能随意修改,所有修改请求都必须通过调用 ViewModel 中的函数来间接完成。
3.Compose State: UI 的直接消费者
虽然可以直接在 Compose 中使用.collectAsState()来订阅 StateFlow,但官方更推荐的方式是使用.collectAsStateWithLifecycle()。 这个函数扮演了连接 StateFlow 和 Compose UI 的桥梁。
collectAsStateWithLifecycle() 的关键优势在于它能感知生命周期。它只会在 UI 对用户可见时(即生命周期至少处于STARTED 状态)才开始收集数据流,并在 UI 进入后台时(生命周期进入 STOPPED状态)自动停止收集。这可以有效避免不必要的资源消耗和潜在的内存泄漏。
Compose State是 StateFlow 在 Compose 世界中的"代理"或"订阅者"。
转换过程:
- collectAsStateWithLifecycle() 会订阅 ViewModel 中暴露的 StateFlow。
- 它会将从 StateFlow 接收到的最新值转换成一个 Compose 的 State 对象。
- 当 StateFlow 发出新值时,这个 State 对象的值会随之更新。
- 由于 Composable 函数读取了这个 State 对象的值,任何值的更新都会自动触发该 Composable 函数及其子函数的重组 (Recomposition),从而刷新 UI 以反映最新的状态。
示例:
kotlin
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
// 订阅 StateFlow 并将其转换为 Compose 的 State
// 当 viewModel.uiState 的值改变时,uiState 的值也会改变,触发重组
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// 根据 uiState 的当前值来构建 UI
when (val state = uiState) {
is MyUiState.Loading -> {
CircularProgressIndicator()
}
is MyUiState.Success -> {
Text(text = state.data)
}
is MyUiState.Error -> {
Text(text = "Error: ${state.message}")
}
}
}
通过这种方式,MutableStateFlow、StateFlow和 Compose 的 State形成了一个清晰、健壮且高效的状态管理模式,是现代 Android 应用开发的基石。
4.LiveData和StateFlow
LiveData也可以在ViewModel中持有数据 ,并且在数据更新的时候触发界面的更新,也是实现响应式编程的一种常用的工具。 LiveData可以在Compose组件中转换成State,并通过委托的形式去使用:
kotlin
val data: String? by myViewModel.myLiveData.observeAsState()
可以发现,LiveData和StateFlow使用场景和使用方法非常相似。但他们也有不同点:
- LiveData是纯Android平台的;而StateFlow是基于Kotlin的,可以
跨平台。 - LiveData初始的时候是没有值的,但是初始化界面总是需要对应一个状态,这就可能产生问题;而StateFlow是有
初始值的。 - LiveData只能在主线程更新数据
setValue,或者子线程postValue;StateFlow拥有FloatOn这样的操作符,可以让数据上游在子线程发射,而数据下游在主线程接收。(线程切换这点,我觉得差不多) - LiveData只能做简单的数据触发;但是StateFlow拥有强大的操作符,数据控制和转换更灵活,比如可以轻松实现
防抖(debounce)
举例如下: 实现一个搜索场景的防抖,通常我们不需要每次输入一个字符都需要去发起一起搜索,而是需要等一个小间隙,用户输入差不多停止的时候,再发起搜索。
kotlin
class SearchViewModel : ViewModel() {
private val _searchQuery = MutableStateFlow("")
// 使用 flow 操作符链来处理逻辑
@OptIn(FlowPreview::class)
val searchResult: Flow<List<String>> = _searchQuery
.debounce(500L) // 防抖操作符!在这里等待500毫秒
.distinctUntilChanged() // 只有当值真正改变时才继续(例如,从 "A" -> "B",而不是 "A" -> "A")
.flatMapLatest { query -> // 当新 query 到来时,取消上一次的网络请求
if (query.isBlank()) {
flowOf(emptyList()) // 如果输入为空,立即返回空列表
} else {
api.search(query) // 执行真正的网络请求,它返回一个 Flow<List<String>>
}
}
// 界面调用的函数,用于更新搜索词
fun onQueryChanged(query: String) {
_searchQuery.value = query
}
}