MutableStateFlow 相关笔记

0 阅读3分钟

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)的推荐做法