MVI架构下不同Intent处理器间共享通用状态逻辑的方案咨询
我之前在做Android MVI项目的时候,也碰到过完全一样的问题——每个页面单独定义的Sealed Intent里,导航、返回这类通用逻辑重复得满天飞,改起来要改N个地方,特别麻烦。下面是我实践下来觉得好用的几个方案,你可以根据自己项目的规模和复杂度来选:
方案一:通过继承复用通用Intent定义
最直接的方式是把所有通用的Intent抽成一个基类密封类,然后各个页面的Intent密封类去继承它。这样每个页面的Intent天然就包含了通用逻辑,不用重复定义。
代码示例:
首先定义通用的基类Intent:
sealed class CommonIntent { object NavigateToBack : CommonIntent() data class NavigateToUserInfo(val id: Int) : CommonIntent() // 其他通用的Intent,比如跳转到设置页、打开通知页都可以放这 }
然后让页面专属的Intent继承这个基类:
sealed class SettingIntent : CommonIntent() { data class BecomeAMaster(val status: StatusOfUser) : SettingIntent() // 这里不用再定义NavigateToUserInfo和NavigateToBack了,直接继承自CommonIntent } sealed class SearchIntents : CommonIntent() { data class ChangeText(val text: String) : SearchIntents() object GetData : SearchIntents() object OpenFilters : SearchIntents() object CloseFilters : SearchIntents() object ApplyFilters : SearchIntents() object ResetFilters : SearchIntents() data class FilterAction(val action: FilterIntent) : SearchIntents() }
在ViewModel里处理的时候,只需要在when表达式里先处理通用Intent的逻辑,再处理页面专属的:
fun handleIntent(intent: CommonIntent) { when (intent) { is CommonIntent.NavigateToBack -> navController.popBackStack() is CommonIntent.NavigateToUserInfo -> navController.navigate("user_info/${intent.id}") // 处理页面专属Intent is SettingIntent.BecomeAMaster -> { /* 处理成为大师的逻辑 */ } is SearchIntents.ChangeText -> { /* 处理搜索文本变化 */ } // ...其他页面专属逻辑 } }
这种方式的好处是实现简单,几乎不用改原有代码结构,适合中小型项目。
方案二:通过组合方式嵌入通用Intent
如果不想让页面Intent和通用Intent有继承关系(比如担心基类太庞大,或者页面只需要部分通用Intent),可以用组合的方式——在页面Intent里定义一个专门的类型,用来包裹通用Intent。
代码示例:
先定义独立的通用导航Intent:
sealed class CommonNavigationIntent { object NavigateToBack : CommonNavigationIntent() data class NavigateToUserInfo(val id: Int) : CommonNavigationIntent() }
然后在页面Intent里加入组合类型:
sealed class SettingIntent { data class toUserScreen(val id: Int) : SettingIntent() data class BecomeAMaster(val status: StatusOfUser) : SettingIntent() // 嵌入通用导航Intent data class CommonNavigation(val intent: CommonNavigationIntent) : SettingIntent() } sealed class SearchIntents { data class ChangeText(val text: String) : SearchIntents() object GetData : SearchIntents() object OpenFilters : SearchIntents() // ...其他页面专属Intent // 嵌入通用导航Intent data class CommonNavigation(val intent: CommonNavigationIntent) : SearchIntents() }
处理的时候,在页面ViewModel里先判断是不是CommonNavigation,再转发到通用处理器:
// 可以把通用导航的处理逻辑抽成一个工具类或者BaseViewModel的方法 private fun handleCommonNavigation(intent: CommonNavigationIntent) { when (intent) { CommonNavigationIntent.NavigateToBack -> navController.popBackStack() is CommonNavigationIntent.NavigateToUserInfo -> navController.navigate("user_info/${intent.id}") } } // 页面ViewModel的Intent处理方法 fun handleIntent(intent: SearchIntents) { when (intent) { is SearchIntents.CommonNavigation -> handleCommonNavigation(intent.intent) is SearchIntents.ChangeText -> { /* 处理搜索文本 */ } // ...其他页面专属逻辑 } }
这种方式的优势是耦合度更低,页面可以自由选择是否引入通用Intent,适合大型项目或者需要更灵活Intent结构的场景。
方案三:封装通用Intent处理器(进阶复用)
如果不仅想复用Intent的定义,还想复用Intent的处理逻辑(比如导航的具体实现),可以封装一个BaseViewModel,把通用Intent的处理逻辑放在里面,页面ViewModel继承这个基类就行。
代码示例:
先定义BaseViewModel:
abstract class BaseViewModel(protected val navController: NavController) : ViewModel() { // 通用Intent处理方法 protected open fun handleCommonIntent(intent: CommonIntent) { when (intent) { CommonIntent.NavigateToBack -> navController.popBackStack() is CommonIntent.NavigateToUserInfo -> navController.navigate("user_info/${intent.id}") } } // 每个页面ViewModel必须实现自己的专属Intent处理 abstract fun handleScreenIntent(intent: Any) }
然后页面ViewModel继承BaseViewModel:
class SearchViewModel(navController: NavController) : BaseViewModel(navController) { override fun handleScreenIntent(intent: Any) { when (intent) { is SearchIntents.ChangeText -> { /* 处理搜索文本 */ } is SearchIntents.GetData -> { /* 获取数据 */ } // 通用Intent直接交给父类处理 is CommonIntent -> handleCommonIntent(intent) // ...其他页面专属逻辑 } } }
这种方式可以把通用逻辑的实现代码也完全复用,不用每个页面ViewModel都写一遍导航的具体代码,后期修改通用逻辑的时候只需要改BaseViewModel就行,维护成本极低,我现在在大型项目里就是用的这种方式。
实践小建议
- 如果项目规模小(页面少于10个),方案一最省心,快速解决重复问题;
- 如果项目规模大,或者需要更灵活的Intent结构,方案二或三更合适;
- 不管用哪种方案,通用Intent的命名要尽量清晰,比如
CommonNavigationIntent比CommonIntent更明确,避免后期混淆; - 可以把通用Intent的处理逻辑(比如导航)抽成独立的
NavigationHandler单例,这样不仅ViewModel可以用,其他地方也能复用。
希望这些方案能帮到你,要是有更复杂的场景,咱们再一起讨论~




