FlowMVI多平台实战:从MVVM迁移到MVI的7个关键步骤
【免费下载链接】FlowMVIArchitecture Framework for Kotlin. Reuse every line of code. Handle all errors automatically. No boilerplate. Build features in minutes. Analytics, metrics, debugging in 3 lines of code. Make all code thread-safe. 50+ features.项目地址: https://gitcode.com/gh_mirrors/fl/FlowMVI
FlowMVI是一个功能强大的Kotlin多平台架构框架,它能帮助开发者复用代码、自动处理错误、减少模板代码,并在几分钟内构建功能完善的应用。本文将详细介绍从传统MVVM架构迁移到FlowMVI的7个关键步骤,帮助开发者快速掌握这一现代架构模式。
1. 理解MVVM与FlowMVI的核心概念差异
在开始迁移之前,首先需要理解MVVM和FlowMVI之间的核心概念映射关系。这将帮助你更好地把握架构转换的本质。
FlowMVI架构图:展示了Store、Plugin和Subscriber之间的交互关系
| MVVM | FlowMVI | 说明 |
|---|---|---|
ViewModel | Container/ViewModel实现ImmutableContainer | 可以保留ViewModel或使用ContainerViewModel |
MutableStateFlow/LiveData | MVIState+updateState {} | 不可变的、基于复制的状态 |
无直接对应StateFlow.value的API | withState {} | 读取始终在事务内进行,避免竞争条件的直接属性访问 |
collectAsStateWithLifecycle | store.subscribe()/ Composesubscribe {} | 简化的订阅API |
| 公开的ViewModel函数 | MVIIntent密封类或store.intent {}lambda | 每个项目选择一种风格 |
SharedFlow/Channel事件 | action()副作用 | 使用action()发送,在subscribe { consume { } }中消费 |
init {}块 | init插件 | 每次store启动时运行 |
viewModelScope.launch {} | PipelineContext.launch {} | 结构化的管道 |
combine()/collect()on flows | whileSubscribed {}插件 | 随订阅者生命周期自动取消 |
try/catch | recover {}插件 | 集中式、可组合的错误处理 |
2. 选择合适的首个迁移功能
增量迁移是成功的关键。选择一个简单、自包含的屏幕开始,例如设置页面或详情屏幕,避免选择具有分页或深度导航的复杂屏幕作为首次迁移目标。
FlowMVI示例应用:展示了使用FlowMVI构建的最简单功能
3. 定义状态和操作模型
在FlowMVI中,状态和操作是核心概念。将MVVM中的状态拆分为密封接口,并明确定义操作:
// FlowMVI状态定义 sealed interface ProfileState : MVIState { data object Loading : ProfileState data class Error(val message: String) : ProfileState data class DisplayingProfile(val profile: Profile) : ProfileState } // FlowMVI操作定义 sealed interface ProfileAction : MVIAction { data class ShowToast(val message: String) : ProfileAction }4. 使用MVVM+模式改造现有ViewModel
不必完全重写ViewModel,可以采用MVVM+模式,在现有ViewModel中实现ImmutableContainer接口,逐步过渡到FlowMVI:
class ProfileViewModel( private val repo: ProfileRepository, ) : ViewModel(), ImmutableContainer<ProfileState, LambdaIntent<ProfileState, ProfileAction>, ProfileAction> { override val store by lazyStore( initial = ProfileState.Loading, scope = viewModelScope, ) { configure { debuggable = BuildFlags.debuggable name = "ProfileStore" } recover { e -> updateState { ProfileState.Error(e.message ?: "Unknown error") } null } whileSubscribed { repo.observeProfile().collect { profile -> updateState { ProfileState.DisplayingProfile(profile) } } } reduceLambdas() } fun saveProfile(name: String) = store.intent { repo.saveProfile(name) action(ProfileAction.ShowToast("Profile saved")) } }5. 迁移UI层代码
FlowMVI提供了简化的订阅API,大幅减少了UI层的模板代码。
Compose UI迁移
之前(MVVM):
@Composable fun ProfileScreen(viewModel: ProfileViewModel = viewModel()) { val snackbarHostState = remember { SnackbarHostState() } val state by viewModel.state.collectAsStateWithLifecycle() state.userMessage?.let { message -> LaunchedEffect(message) { snackbarHostState.showSnackbar(message) viewModel.userMessageShown() } } if (state.isLoading) { CircularProgressIndicator() } else { state.profile?.let { ProfileContent(it, onSave = viewModel::saveProfile) } } }之后(FlowMVI):
@Composable fun ProfileScreen(viewModel: ProfileViewModel = viewModel()) = with(viewModel.store) { val state by subscribe { action -> when (action) { is ShowToast -> { /* show snackbar */ } } } when (state) { is Loading -> CircularProgressIndicator() is DisplayingProfile -> ProfileContent(state.profile, onSave = viewModel::saveProfile) is Error -> ErrorContent(state.message) } }Android Views迁移
之前(MVVM):
class ProfileFragment : Fragment() { private val viewModel: ProfileViewModel by viewModels() private val binding by viewBinding<ProfileFragmentBinding>() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { launch { viewModel.state.collect(::render) } launch { viewModel.events.collect(::handleEvent) } } } } }之后(FlowMVI):
class ProfileFragment : Fragment() { private val viewModel: ProfileViewModel by viewModels() private val binding by viewBinding<ProfileFragmentBinding>() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) subscribe(viewModel.store, ::consume, ::render) } private fun render(state: ProfileState) { /* update all views */ } private fun consume(action: ProfileAction) { /* handle side effects */ } }6. 配置调试工具
FlowMVI提供了强大的调试工具,可以帮助你跟踪状态变化和操作流程。配置调试器只需简单几步:
FlowMVI调试器设置界面:简单配置即可开始调试
在store配置中启用调试:
configure { debuggable = BuildFlags.debuggable name = "ProfileStore" // 为调试器提供有意义的名称 }7. 逐步采用高级功能和插件
FlowMVI提供了丰富的插件生态系统,可以根据需求逐步采用:
whileSubscribed:处理响应式数据流,从第一天就应该使用enableLogging():在共享DI配置中设置一次,而非每个store单独设置saveState/parcelizeState:实现状态持久化,详情参见状态持久化文档
常见挑战与解决方案
副作用传递
问题:基于Channel的事件如果收集太晚或被错误的订阅者收集可能会丢失。
解决方案:FlowMVI默认使用ActionShareBehavior.Distribute(),它以扇出FIFO方式对操作进行排队,操作会等待订阅者,不会被丢弃。
线程和并发
问题:手动切换调度器(如Dispatchers.IO)并保护MutableStateFlow免受竞争条件影响。
解决方案:updateState {}默认是线程安全的(使用Atomic状态策略)。如需覆盖调度器,可在configure {}中设置一次:
configure { coroutineContext = Dispatchers.Default }状态恢复
问题:在每个ViewModel中手动连接SavedStateHandle。
解决方案:只需一个插件调用:
parcelizeState(handle) // Android // 或 serializeState(path, serializer) // KMP总结
从MVVM迁移到FlowMVI是一个渐进式过程,通过遵循上述7个关键步骤,你可以平滑过渡到这个功能强大的多平台架构框架。FlowMVI不仅能减少模板代码,还提供了强大的状态管理、错误处理和调试能力,帮助你构建更健壮、可维护的应用。
要开始使用FlowMVI,只需克隆仓库:
git clone https://gitcode.com/gh_mirrors/fl/FlowMVI更多详细信息,请参考官方文档和快速入门指南。
【免费下载链接】FlowMVIArchitecture Framework for Kotlin. Reuse every line of code. Handle all errors automatically. No boilerplate. Build features in minutes. Analytics, metrics, debugging in 3 lines of code. Make all code thread-safe. 50+ features.项目地址: https://gitcode.com/gh_mirrors/fl/FlowMVI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考