25、Flow与架构模式:MVVM中的Flow、MVI中的Flow、Clean Architecture中的Flow
聊到Flow,就绕不开架构模式。说实话,很多初学者把Flow当成一个“高级版LiveData”来用,这其实有点浪费。Flow真正的威力,是在合适的架构里才能完全释放出来。今天我就结合自己的项目经验,聊聊Flow在MVVM、MVI和Clean Architecture中分别该怎么用。
一、MVVM中的Flow:最自然的组合
MVVM是我用得最多的架构。ViewModel + Flow,简直是天生一对。为什么?因为ViewModel的生命周期和Flow的冷热流特性,配合得刚刚好。
核心思路:ViewModel持有Flow,View层收集Flow。数据变化自动通知UI更新。
我在项目中习惯这样组织代码:
// ViewModel层
class UserViewModel(
private val userRepository: UserRepository
) : ViewModel() {
// 使用StateFlow暴露状态,保证UI始终有值
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
init {
loadUsers()
}
private fun loadUsers() {
viewModelScope.launch {
userRepository.getUsers()
.catch { e ->
_uiState.value = UiState.Error(e.message ?: "未知错误")
}
.collect { users ->
_uiState.value = UiState.Success(users)
}
}
}
}
// View层(Activity/Fragment)
class UserFragment : Fragment() {
private val viewModel: UserViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 收集Flow,自动处理生命周期
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
renderUi(state)
}
}
}
}
}
我的经验:一定要用repeatOnLifecycle来收集Flow。我曾经踩过坑——直接在Fragment的onCreateView里用lifecycleScope.launch收集,结果Fragment进入后台后还在收集数据,导致内存泄漏。repeatOnLifecycle会在生命周期低于STARTED时自动取消协程,安全又省心。
二、MVI中的Flow:单向数据流的完美载体
MVI(Model-View-Intent)模式,说白了就是把用户操作也变成数据流。Intent(用户意图)→ Model(处理逻辑)→ View(渲染UI),整个环路由Flow串联起来。
我个人觉得,MVI比MVVM更适合复杂交互场景。比如表单校验、多步骤操作,用MVI写出来逻辑特别清晰。
// 定义Intent(用户操作)
sealed class LoginIntent {
data class EmailChanged(val email: String) : LoginIntent()
data class PasswordChanged(val password: String) : LoginIntent()
object LoginClicked : LoginIntent()
}
// 定义State(UI状态)
data class LoginState(
val email: String = "",
val password: String = "",
val isLoading: Boolean = false,
val error: String? = null
)
// ViewModel处理Intent流
class LoginViewModel : ViewModel() {
private val _intent = MutableSharedFlow<LoginIntent>()
private val _state = MutableStateFlow(LoginState())
val state: StateFlow<LoginState> = _state.asStateFlow()
init {
// 将Intent流转换为State流
viewModelScope.launch {
_intent
.scan(LoginState()) { state, intent ->
when (intent) {
is LoginIntent.EmailChanged -> state.copy(email = intent.email)
is LoginIntent.PasswordChanged -> state.copy(password = intent.password)
is LoginIntent.LoginClicked -> {
// 执行登录逻辑
state.copy(isLoading = true)
}
}
}
.collect { newState ->
_state.value = newState
}
}
}
fun processIntent(intent: LoginIntent) {
viewModelScope.launch {
_intent.emit(intent)
}
}
}
注意:MVI中我推荐用SharedFlow来处理Intent,而不是StateFlow。因为Intent是事件,不需要保留最新值。用SharedFlow可以避免重复消费的问题。我曾经在项目里用StateFlow存Intent,结果用户快速点击按钮时,中间状态被跳过了,排查了好久才发现是StateFlow的粘性特性导致的。
三、Clean Architecture中的Flow:跨层传递的利器
Clean Architecture强调分层:Data层 → Domain层 → Presentation层。Flow在这里扮演的角色,就是跨层传递数据的“管道”。
你想想看,如果每层都用回调或者接口,代码会变得多复杂?Flow天然支持异步、背压、变换操作,简直是Clean Architecture的标配。
| 层级 | Flow的使用方式 | 我的建议 |
|---|---|---|
| Data层 | 从网络/数据库返回Flow<T> | Room和Retrofit都原生支持Flow,直接用 |
| Domain层 | Repository接口返回Flow<T> | UseCase中可以用Flow的变换操作符做业务逻辑 |
| Presentation层 | ViewModel收集Flow并转为StateFlow | 用StateFlow暴露给UI,保证线程安全 |
// Data层 - Room DAO
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getUsers(): Flow<List<UserEntity>> // Room自动返回Flow
}
// Domain层 - Repository接口
interface UserRepository {
fun getUsers(): Flow<List<User>> // 跨层传递Flow
}
// Domain层 - UseCase
class GetUsersUseCase(
private val userRepository: UserRepository
) {
operator fun invoke(): Flow<List<User>> {
return userRepository.getUsers()
.map { users -> users.filter { it.isActive } } // 业务过滤
.flowOn(Dispatchers.IO) // 指定调度器
}
}
// Presentation层 - ViewModel
class UserViewModel(
private val getUsersUseCase: GetUsersUseCase
) : ViewModel() {
val users: StateFlow<List<User>> = getUsersUseCase()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
}
避坑指南:我曾经在Clean Architecture项目里犯过一个错误——在UseCase里直接调用了.collect()。结果UseCase变成了冷流的一次性消费,后续的数据更新完全收不到。记住:UseCase应该返回Flow,而不是消费Flow。消费Flow是ViewModel的事。
四、三种架构模式的对比
说了这么多,我们来总结一下。三种架构模式对Flow的使用各有侧重:
- MVVM:StateFlow + ViewModel,适合中小型项目,简单直接
- MVI:SharedFlow + StateFlow + 单向数据流,适合复杂交互场景
- Clean Architecture:Flow跨层传递,适合大型项目,职责清晰
我个人建议:如果项目刚开始,从MVVM入手最稳妥。等业务复杂到一定程度,再逐步引入MVI和Clean Architecture。别一开始就上最复杂的架构,容易把自己绕进去。
嗯,以上就是我对Flow在三种架构模式中的理解。说白了,Flow就是个工具,关键看你怎么用它。选对架构模式,Flow能帮你写出更清晰、更可维护的代码。选错了,反而会增加复杂度。多在实践中摸索,慢慢就有感觉了。