遵循Google整洁架构时,多步骤表单场景下ViewModel间共享数据的合理方案
我很理解你现在的纠结——既想严格遵循Google整洁架构的规则,又不想为了共享表单数据破坏各ViewModel的单一职责,还不想把Feature的内部细节泄露到全局层。结合整洁架构的核心原则和Android组件特性,给你几个落地性极强的方案:
方案一:Feature层专属的表单状态持有者(最贴合整洁架构)
在你的表单Feature模块内部,创建一个属于Feature领域层的FormStateHolder类(也可以叫FormCoordinator),这个类完全是Feature私有的,绝对不会暴露到全局Data层。它的职责非常清晰:
- 持有完整的表单状态对象(比如
UserRegistrationForm,这个模型也是Feature内部的领域模型,不用放到全局Data层) - 提供细粒度的方法让各个ViewModel更新自己负责的字段(比如
updatePersonalInfo(name: String, email: String)、updateShippingAddress(address: Address)) - 可以暴露
Flow<FormState>让ViewModel监听整体状态变化(如果需要跨步骤联动的话)
然后通过依赖注入(比如Hilt)把这个FormStateHolder注入到每个步骤的ViewModel中。这里一定要注意:不要把它做成全局单例,而是让它的生命周期和表单流程绑定——比如用Assisted Inject结合导航组件的BackStackEntry,或者让它和表单子图的生命周期同步,这样用户中途退出表单时,状态会自动清理,完全避免内存泄漏。
这个方案的优势太明显了:
- 每个ViewModel依然保持单一职责,只处理当前步骤的验证、数据获取逻辑,完全不用关心其他步骤的细节
- 表单的所有状态和逻辑都在Feature内部闭环,完美符合整洁架构“内层(领域)不依赖外层,外层依赖内层”的核心规则
- 不会把Feature的私有业务模型泄露到全局Data层,彻底避免污染全局架构
方案二:利用Jetpack Navigation的BackStackEntry共享状态
如果你正在用Jetpack Navigation管理表单的子图,这是一个更轻量的Android原生方案:
- 在导航图中定义你的表单子图(比如
form_nav_graph) - 每个步骤的ViewModel通过导航控制器获取这个子图对应的BackStackEntry:
val formBackStackEntry = navController.getBackStackEntry(R.id.form_nav_graph) val savedStateHandle = formBackStackEntry.savedStateHandle - 把完整的表单状态存在这个
savedStateHandle里,每个ViewModel只更新自己负责的字段,比如:// 个人信息步骤的ViewModel fun onNameChanged(name: String) { savedStateHandle.set("personal_name", name) }
这个方案的好处是:
- 状态和表单导航流程的生命周期完全绑定,不用自己手动管理状态的创建和销毁,自动规避内存泄漏
- 不需要额外创建Holder类,直接利用Android原生组件能力就能实现状态共享
- 每个ViewModel依然专注于当前步骤的UI逻辑,职责划分清晰
方案三:Feature级别的UseCase+私有Repository
如果想更贴合整洁架构的“用例驱动”思想,可以在Feature的领域层做如下设计:
- 创建Feature私有的
FormRepository,负责持有表单状态(只是内存中的临时状态,不需要和服务器交互) - 为每个步骤创建对应的UseCase,比如
UpdatePersonalInfoUseCase、ValidateShippingAddressUseCase,这些UseCase依赖FormRepository来更新和读取状态 - 每个ViewModel注入对应的UseCase,只调用UseCase来处理当前步骤的逻辑,不需要直接操作完整的表单状态
比如,个人信息步骤的ViewModel只需要调用updatePersonalInfoUseCase.execute(name, email),而UseCase会去更新FormRepository里的状态。如果某个步骤需要依赖之前的字段,也可以通过FormRepository暴露的Flow来监听。
这个方案的优势是把表单的业务逻辑完全封装在领域层,ViewModel只做UI和领域层的桥梁,完美契合整洁架构的分层职责。
对你之前顾虑的补充说明
- 关于“把Form模型放到全局Data层”:确实完全不推荐,因为表单是Feature特有的临时业务状态,不属于全局Data层的通用实体(全局Data层应该放和服务器交互的通用模型,比如
User实体),把表单模型放到全局层会彻底破坏架构的边界。 - 关于“单个大ViewModel”:会导致ViewModel的职责过于庞杂,不仅要处理所有步骤的验证、数据逻辑,还要维护所有字段的状态,后期维护和测试都会变得异常麻烦,严重违反单一职责原则。
- 关于“Feature内的单例Holder”:单例的核心问题是生命周期不受控,比如用户中途退出表单后,单例还会保留之前的状态,可能导致下一次进入表单时出现脏数据;另外全局单例也不符合整洁架构“依赖注入而非硬编码单例”的思想,所以更推荐用和流程生命周期绑定的Holder。
内容来源于stack exchange

