Kotlin MutableStateFlow 和 asStateFlow 详解
这两个是 Kotlin 协程 Flow API 中的核心概念,用于状态管理和响应式编程。
基本概念
MutableStateFlow - 可变的 StateFlow
// 创建一个可变的 StateFlow
private val _counter = MutableStateFlow(0) // 初始值为 0
// 可以修改它的值
_counter.value = 1 // 直接赋值
_counter.tryEmit(2) // 另一种修改方式
_counter.update { it + 1 } // 基于当前值更新
asStateFlow() - 转换为只读的 StateFlow
// 对外暴露只读版本
val counter: StateFlow<Int> = _counter.asStateFlow()
// UI 层只能观察,不能修改
// counter.value = 5 // 编译错误!不能修改
为什么需要这种模式?
封装原则
class CounterViewModel {
// 私有可变状态
private val _count = MutableStateFlow(0)
// 公开只读状态
val count: StateFlow<Int> = _count.asStateFlow()
// 通过方法修改状态
fun increment() {
_count.value += 1
}
fun reset() {
_count.value = 0
}
}
这种模式确保了:
- ✅ 状态安全:外部不能随意修改状态
- ✅ 单一数据源:所有修改都通过 ViewModel 的方法
- ✅ 可测试性:状态变化可预测
- ✅ 可维护性:修改逻辑集中在一处
完整工作流程
1. ViewModel 内部
class UserViewModel {
// 私有可变状态
private val _userState = MutableStateFlow<UserState>(UserState.Loading)
private val _isLoading = MutableStateFlow(false)
// 公开只读状态
val userState: StateFlow<UserState> = _userState.asStateFlow()
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
// 修改状态的方法
suspend fun loadUser() {
_isLoading.value = true
try {
val user = apiService.getUser()
_userState.value = UserState.Success(user)
} catch (e: Exception) {
_userState.value = UserState.Error(e.message ?: "Unknown error")
} finally {
_isLoading.value = false
}
}
fun updateUserName(name: String) {
val current = _userState.value
if (current is UserState.Success) {
_userState.value = current.copy(user = current.user.copy(name = name))
}
}
}
sealed class UserState {
object Loading : UserState()
data class Success(val user: User) : UserState()
data class Error(val message: String) : UserState()
}
2. UI 层(Compose)
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
// 收集状态(自动重绘)
val userState by viewModel.userState.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
Column {
if (isLoading) {
CircularProgressIndicator()
}
when (val state = userState) {
is UserState.Loading -> Text("Loading...")
is UserState.Success -> {
Text("Name: ${state.user.name}")
Text("Email: ${state.user.email}")
Button(onClick = { viewModel.updateUserName("New Name") }) {
Text("Update Name")
}
}
is UserState.Error -> Text("Error: ${state.message}")
}
}
}
3. UI 层(Android View 系统)
class UserActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 收集 Flow(使用 lifecycleScope)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.userState.collect { state ->
updateUI(state)
}
}
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.isLoading.collect { loading ->
progressBar.isVisible = loading
}
}
}
}
private fun updateUI(state: UserState) {
when (state) {
is UserState.Loading -> showLoading()
is UserState.Success -> showUser(state.user)
is UserState.Error -> showError(state.message)
}
}
}
StateFlow 的核心特性
1. 必须有初始值
// 必须提供初始值
val state1 = MutableStateFlow("Initial") // ✓
val state2: MutableStateFlow<String> // ✗ 编译错误
2. 值相等性检查
val state = MutableStateFlow("Hello")
// 如果新值等于当前值,不会触发收集者
state.value = "Hello" // 不会通知收集者
state.value = "World" // 会通知收集者
// 自定义对象需要正确实现 equals()
data class User(val id: String, val name: String)
val userFlow = MutableStateFlow(User("1", "Alice"))
userFlow.value = User("1", "Alice") // 不会触发,因为 equals 返回 true
userFlow.value = User("1", "Bob") // 会触发
3. 多个收集者
val counter = MutableStateFlow(0)
// 第一个收集者
coroutineScope.launch {
counter.collect { value ->
println("Collector 1: $value")
}
}
// 第二个收集者(会立即收到当前值 0)
coroutineScope.launch {
counter.collect { value ->
println("Collector 2: $value")
}
}
counter.value = 1
// 输出:
// Collector 1: 1
// Collector 2: 1
实际应用模式
1. 搜索功能
class SearchViewModel {
private val _searchQuery = MutableStateFlow("")
private val _searchResults = MutableStateFlow<List<Result>>(emptyList())
val searchQuery: StateFlow<String> = _searchQuery.asStateFlow()
val searchResults: StateFlow<List<Result>> = _searchResults.asStateFlow()
init {
// 监听搜索词变化
viewModelScope.launch {
_searchQuery
.debounce(300) // 防抖 300ms
.distinctUntilChanged() // 值变化时才触发
.filter { it.length >= 3 } // 至少3个字符
.collect { query ->
performSearch(query)
}
}
}
fun onQueryChanged(query: String) {
_searchQuery.value = query
}
private suspend fun performSearch(query: String) {
val results = searchRepository.search(query)
_searchResults.value = results
}
}
2. 表单验证
class RegistrationViewModel {
private val _email = MutableStateFlow("")
private val _password = MutableStateFlow("")
private val _isValid = MutableStateFlow(false)
val email: StateFlow<String> = _email.asStateFlow()
val password: StateFlow<String> = _password.asStateFlow()
val isValid: StateFlow<Boolean> = _isValid.asStateFlow()
init {
// 组合多个状态
viewModelScope.launch {
combine(_email, _password) { email, password ->
validateForm(email, password)
}.collect { valid ->
_isValid.value = valid
}
}
}
fun setEmail(email: String) {
_email.value = email
}
fun setPassword(password: String) {
_password.value = password
}
private fun validateForm(email: String, password: String): Boolean {
return email.isNotEmpty() &&
email.contains("@") &&
password.length >= 6
}
}
3. 多步骤流程
class CheckoutViewModel {
private val _currentStep = MutableStateFlow(CheckoutStep.CART)
private val _stepsCompleted = MutableStateFlow(setOf<CheckoutStep>())
val currentStep: StateFlow<CheckoutStep> = _currentStep.asStateFlow()
val stepsCompleted: StateFlow<Set<CheckoutStep>> = _stepsCompleted.asStateFlow()
fun goToStep(step: CheckoutStep) {
_currentStep.value = step
}
fun markStepCompleted(step: CheckoutStep) {
_stepsCompleted.update { it + step }
}
fun canProceedTo(step: CheckoutStep): Boolean {
return step.previousSteps.all { it in _stepsCompleted.value }
}
}
enum class CheckoutStep(val previousSteps: List<CheckoutStep> = emptyList()) {
CART,
ADDRESS(listOf(CART)),
PAYMENT(listOf(CART, ADDRESS)),
CONFIRMATION(listOf(CART, ADDRESS, PAYMENT))
}
最佳实践
1. 使用 update 方法
// 而不是
_counter.value = _counter.value + 1
// 使用
_counter.update { it + 1 } // 更安全,避免并发问题
2. 处理资源释放
class MyViewModel : ViewModel() {
private val _data = MutableStateFlow<List<Data>>(emptyList())
val data: StateFlow<List<Data>> = _data.asStateFlow()
// ViewModel 销毁时自动清理
init {
viewModelScope.launch {
repository.getDataFlow().collect { newData ->
_data.value = newData
}
}
}
}
3. 避免在 UI 层直接修改
// ❌ 错误做法 - UI 直接修改状态
class MyActivity : AppCompatActivity() {
val viewModel = MyViewModel()
fun badPractice() {
// 不要这样做!
viewModel._counter.value = 10 // 暴露了可变状态
}
}
// ✅ 正确做法 - 通过 ViewModel 方法
viewModel.incrementCounter()
4. 测试状态变化
@Test
fun testCounterIncrement() = runTest {
val viewModel = CounterViewModel()
// 收集状态变化
val values = mutableListOf<Int>()
val job = launch {
viewModel.count.collect { values.add(it) }
}
// 初始值
assertEquals(0, viewModel.count.value)
// 执行操作
viewModel.increment()
// 验证状态变化
assertEquals(listOf(0, 1), values)
job.cancel()
}
常见问题
1. 什么时候用 StateFlow?什么时候用 SharedFlow?
// StateFlow - 需要维护当前状态
val currentUser: StateFlow<User?> // 总是有当前值
// SharedFlow - 事件流,不需要维护状态
val snackbarMessages: SharedFlow<String> // 事件,不需要重放
2. StateFlow vs LiveData
// LiveData (Android)
val liveData = MutableLiveData<String>()
liveData.value = "Hello"
// StateFlow (跨平台)
val stateFlow = MutableStateFlow("Hello")
stateFlow.value = "World"
// StateFlow 优势:
// - 支持协程
// - 支持操作符(map、filter、combine等)
// - 跨平台(Android、iOS、桌面、Web)
// - 更灵活的生命周期管理
总结:
MutableStateFlow:可变的 StateFlow,用于 ViewModel 内部存储和修改状态asStateFlow():将可变状态转换为只读状态,对外暴露- 这种模式确保了单向数据流和状态封装,是现代 Android 开发(尤其是 Compose)的推荐做法