Android:基于MVVM/MVI模式实现Jetpack Compose Fragment间事件通知
基于MVVM+MVI的跨Fragment通信实现方案
方案一:共享ViewModel(推荐,符合MVI单向数据流)
在MVI架构中,所有状态变化遵循单向流规则,跨Fragment通信最直接的方式是共享同一个绑定到宿主Activity(或NavGraph)的ViewModel实例,两个Fragment通过观察ViewModel的状态变化实现联动。
实现步骤:
定义MVI的状态与意图
先明确ViewModel要处理的用户意图(Intent)和对外暴露的状态(State),包含触发目标Fragment操作的标记:// 共享状态类,不可变,包含触发操作的标记 data class SharedState( val shouldTriggerAction: Boolean = false ) // 意图密封类,定义用户可触发的行为 sealed class SharedIntent { object TriggerTargetAction : SharedIntent() }实现共享ViewModel
ViewModel处理Intent并生成新的State,保证状态单向更新:class SharedViewModel : ViewModel() { private val _state = MutableStateFlow(SharedState()) val state: StateFlow<SharedState> = _state.asStateFlow() fun handleIntent(intent: SharedIntent) { when (intent) { SharedIntent.TriggerTargetAction -> { // 更新状态,标记需要触发操作 _state.update { it.copy(shouldTriggerAction = true) } // 重置标记,避免重复触发(按需调整逻辑) viewModelScope.launch { delay(100) // 确保观察者收到事件后重置 _state.update { it.copy(shouldTriggerAction = false) } } } } } }发送方Fragment(带按钮的Compose视图)
通过activityViewModels()获取共享ViewModel,点击按钮时发送Intent:class SenderFragment : Fragment() { private val viewModel: SharedViewModel by activityViewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { return ComposeView(requireContext()).apply { setContent { Button( onClick = { viewModel.handleIntent(SharedIntent.TriggerTargetAction) } ) { Text("触发目标Fragment操作") } } } } }接收方Fragment
观察ViewModel的State,当标记为true时执行对应操作:class ReceiverFragment : Fragment() { private val viewModel: SharedViewModel by activityViewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { return ComposeView(requireContext()).apply { setContent { val state by viewModel.state.collectAsStateWithLifecycle() LaunchedEffect(state.shouldTriggerAction) { if (state.shouldTriggerAction) { performTargetAction() } } Text("接收方Fragment") } } } private fun performTargetAction() { // 这里编写具体操作逻辑,比如更新UI、请求数据等 Toast.makeText(requireContext(), "收到触发指令", Toast.LENGTH_SHORT).show() } }
注意事项:
- 必须使用
activityViewModels()获取ViewModel,确保两个Fragment拿到同一实例。 - 状态重置逻辑根据需求调整:如果需要单次触发就重置标记;如果需要持续监听,可保留状态直到操作完成后再重置。
方案二:基于Event包装类的一次性事件通信
如果需要传递一次性事件(避免状态污染),可以用Event包装类封装事件,确保事件只被消费一次。
实现步骤:
定义Event包装类
class Event<out T>(private val content: T) { private var hasBeenHandled = false // 获取未被处理的事件内容 fun getContentIfNotHandled(): T? { return if (hasBeenHandled) { null } else { hasBeenHandled = true content } } }更新ViewModel状态
将状态中的触发标记替换为Event:data class SharedState( val triggerActionEvent: Event<Unit>? = null ) class SharedViewModel : ViewModel() { private val _state = MutableStateFlow(SharedState()) val state: StateFlow<SharedState> = _state.asStateFlow() fun handleIntent(intent: SharedIntent) { when (intent) { SharedIntent.TriggerTargetAction -> { _state.update { it.copy(triggerActionEvent = Event(Unit)) } } } } }接收方处理事件
LaunchedEffect(state.triggerActionEvent) { state.triggerActionEvent?.getContentIfNotHandled()?.let { performTargetAction() } }
方案三:结合Navigation组件的结果回调(适合导航关联场景)
如果两个Fragment通过Navigation组件导航关联,可以利用savedStateHandle传递结果,同时结合MVI状态管理:
发送方设置结果
Button(onClick = { findNavController().previousBackStackEntry?.savedStateHandle?.set("trigger_action", true) }) { Text("触发操作") }接收方监听结果
val triggerAction by remember { findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<Boolean>("trigger_action") } ?: mutableLiveDataOf(false) LaunchedEffect(triggerAction) { if (triggerAction) { performTargetAction() // 重置结果,避免重复触发 findNavController().currentBackStackEntry?.savedStateHandle?.set("trigger_action", false) } }
总结
- 优先选择共享ViewModel+MVI状态流,符合单向数据流设计原则,代码可控性强。
- 一次性事件推荐用Event包装类,避免重复消费。
- 导航场景可结合Navigation的结果回调,但需注意状态重置逻辑。
内容的提问来源于stack exchange,提问作者melikaafrakhteh
相关产品推荐
相关产品推荐

