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在三种架构模式中的角色 MVVM View → ViewModel → Model StateFlow LiveData替代方案 生命周期感知 简单直接 适合中小型项目 MVI Intent → Model → View SharedFlow(Intent) StateFlow(State) 单向数据流 可预测性强 适合复杂交互 Clean Arch Data → Domain → Presentation Flow跨层传递 Repository返回Flow UseCase变换Flow ViewModel收集Flow 适合大型项目 核心原则:Flow作为数据管道,连接各层,保持响应式数据流

嗯,以上就是我对Flow在三种架构模式中的理解。说白了,Flow就是个工具,关键看你怎么用它。选对架构模式,Flow能帮你写出更清晰、更可维护的代码。选错了,反而会增加复杂度。多在实践中摸索,慢慢就有感觉了。

公众号:蓝海资料掘金营,微信deep3321