You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android:基于MVVM/MVI模式实现Jetpack Compose Fragment间事件通知

基于MVVM+MVI的跨Fragment通信实现方案

方案一:共享ViewModel(推荐,符合MVI单向数据流)

在MVI架构中,所有状态变化遵循单向流规则,跨Fragment通信最直接的方式是共享同一个绑定到宿主Activity(或NavGraph)的ViewModel实例,两个Fragment通过观察ViewModel的状态变化实现联动。

实现步骤:

  1. 定义MVI的状态与意图
    先明确ViewModel要处理的用户意图(Intent)和对外暴露的状态(State),包含触发目标Fragment操作的标记:

    // 共享状态类,不可变,包含触发操作的标记
    data class SharedState(
        val shouldTriggerAction: Boolean = false
    )
    
    // 意图密封类,定义用户可触发的行为
    sealed class SharedIntent {
        object TriggerTargetAction : SharedIntent()
    }
    
  2. 实现共享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) }
                    }
                }
            }
        }
    }
    
  3. 发送方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操作")
                    }
                }
            }
        }
    }
    
  4. 接收方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包装类封装事件,确保事件只被消费一次。

实现步骤:

  1. 定义Event包装类

    class Event<out T>(private val content: T) {
        private var hasBeenHandled = false
    
        // 获取未被处理的事件内容
        fun getContentIfNotHandled(): T? {
            return if (hasBeenHandled) {
                null
            } else {
                hasBeenHandled = true
                content
            }
        }
    }
    
  2. 更新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)) }
                }
            }
        }
    }
    
  3. 接收方处理事件

    LaunchedEffect(state.triggerActionEvent) {
        state.triggerActionEvent?.getContentIfNotHandled()?.let {
            performTargetAction()
        }
    }
    

方案三:结合Navigation组件的结果回调(适合导航关联场景)

如果两个Fragment通过Navigation组件导航关联,可以利用savedStateHandle传递结果,同时结合MVI状态管理:

  1. 发送方设置结果

    Button(onClick = {
        findNavController().previousBackStackEntry?.savedStateHandle?.set("trigger_action", true)
    }) {
        Text("触发操作")
    }
    
  2. 接收方监听结果

    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 20:57:17