11、ViewModel与Fragment通信:Activity共享ViewModel、Fragment间数据共享、实战:主从布局

Fragment之间的通信,说实话,是Android开发里绕不开的一个坎。我记得刚入行那会儿,为了在两个Fragment之间传个数据,愣是写了三四个接口回调,代码又臭又长。后来接触了ViewModel,才发现原来可以这么清爽。

这一章,我们就来聊聊怎么用ViewModel优雅地解决Fragment通信问题。核心思路就一句话:让Activity作为桥梁,ViewModel作为数据容器

为什么Fragment通信这么麻烦?

Fragment的设计初衷是模块化。每个Fragment应该独立、可复用。但现实是,它们经常需要共享数据。比如一个主从布局的新闻App,左边是列表,右边是详情。你点击左边的条目,右边要跟着变化。

传统的做法是用接口回调:FragmentA定义接口,Activity实现它,再传给FragmentB。嗯,能跑,但维护起来很痛苦。尤其是项目大了以后,回调满天飞,你都不知道数据是从哪来的。

我个人习惯用ViewModel来解决这个问题。为什么呢?因为ViewModel的生命周期是跟着Activity走的。只要Fragment依附在同一个Activity上,它们就能共享同一个ViewModel实例。

Activity共享ViewModel:核心原理

说白了,就是让多个Fragment获取同一个ViewModel对象。这个ViewModel由Activity管理,Fragment之间通过它来读写数据。

来看代码。假设我们有一个共享的ViewModel:

class SharedViewModel : ViewModel() {
    private val _selectedItem = MutableLiveData<String>()
    val selectedItem: LiveData<String> get() = _selectedItem

    fun selectItem(item: String) {
        _selectedItem.value = item
    }
}

然后,在FragmentA中,我们这样获取:

class FragmentA : Fragment() {
    private lateinit var viewModel: SharedViewModel

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 注意:这里传的是 activity!不是 fragment
        viewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)

        // 模拟点击事件
        button.setOnClickListener {
            viewModel.selectItem("文章标题 #1")
        }
    }
}

在FragmentB中,同样用 requireActivity() 来获取:

class FragmentB : Fragment() {
    private lateinit var viewModel: SharedViewModel

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)

        viewModel.selectedItem.observe(viewLifecycleOwner) { item ->
            textView.text = "当前选中:$item"
        }
    }
}

关键点就一个:ViewModelProvider 传入的是 requireActivity(),而不是 this。这样拿到的ViewModel就是Activity级别的,所有Fragment共享同一份数据。

核心要点: 只要Fragment依附在同一个Activity上,用 requireActivity() 获取的ViewModel就是同一个实例。数据变化会自动通知所有观察者。

Fragment间数据共享:不只是传值

共享ViewModel的好处不只是传个字符串。我在项目中遇到过更复杂的场景:FragmentA需要通知FragmentB刷新列表,同时FragmentB要告诉FragmentA操作结果。如果用回调,得写一堆接口。用ViewModel,只需要在LiveData里放一个状态对象。

举个例子,我们定义一个数据类:

data class ItemAction(val type: ActionType, val itemId: String)

enum class ActionType {
    DELETE, UPDATE, SELECT
}

然后在SharedViewModel里加一个LiveData:

class SharedViewModel : ViewModel() {
    private val _itemAction = MutableLiveData<ItemAction>()
    val itemAction: LiveData<ItemAction> get() = _itemAction

    fun performAction(action: ItemAction) {
        _itemAction.value = action
    }
}

FragmentA发送动作,FragmentB监听动作。双方完全解耦,不需要知道对方的存在。你想想看,这比接口回调清爽了多少倍?

避坑指南: 我曾经在项目里直接用 MutableLiveData 暴露给外部,结果数据被外部篡改,查了半天。后来养成了习惯:对外只暴露不可变的 LiveData,内部用 MutableLiveData。这是 Kotlin 的规范写法,一定要遵守。

实战:主从布局

主从布局是Fragment通信的经典场景。左边一个列表Fragment,右边一个详情Fragment。点击左边,右边更新。

我们先画一张图,看看整体结构:

MainActivity SharedViewModel selectedItem: LiveData<String> ListFragment 显示文章列表 点击条目 → 调用 viewModel.selectItem() DetailFragment 显示文章详情 观察 selectedItem 自动更新 UI 数据流向:ListFragment → SharedViewModel → DetailFragment 两个 Fragment 完全解耦,只依赖 ViewModel

看到这张图,你应该就明白了。数据流向是单向的:ListFragment修改ViewModel里的数据,DetailFragment观察变化。两个Fragment之间没有直接引用,维护起来特别轻松。

具体实现时,我建议在Activity里初始化好两个Fragment,然后什么都不用管。数据流动完全由ViewModel驱动。代码大概长这样:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (savedInstanceState == null) {
            supportFragmentManager.beginTransaction()
                .add(R.id.fragment_container_left, ListFragment())
                .add(R.id.fragment_container_right, DetailFragment())
                .commit()
        }
    }
}

Activity只负责布局和Fragment的创建,不参与任何数据传递。这就是ViewModel带来的好处——职责清晰,代码干净。

注意事项: 如果Fragment被销毁重建(比如屏幕旋转),ViewModel不会丢失,但LiveData的观察者需要重新绑定。记得在 onViewCreated 里重新 observe,不要在 onCreate 里做。否则可能会收到过期的数据。

总结一下

ViewModel + Fragment通信,说白了就是利用Activity作为作用域,让多个Fragment共享同一个ViewModel实例。这样做的好处很明显:

  • 解耦:Fragment之间不需要互相知道对方的存在
  • 生命周期安全:ViewModel自动处理配置变更,不会丢失数据
  • 代码简洁:不用写一堆接口回调,数据流动清晰可见

我个人觉得,这是Android官方提供的最实用的架构组件之一。你想想看,以前写Fragment通信多痛苦?现在一个ViewModel全搞定。嗯,这就是技术进步带来的幸福感。

下一节我们会继续深入,看看ViewModel配合其他组件还能玩出什么花样。但今天的内容,已经足够你应对大部分Fragment通信场景了。

一句话记住: 用 requireActivity() 获取 ViewModel,让 Activity 做数据中转站,Fragment 只关心自己的 UI 和业务逻辑。

公众号:蓝海资料掘金营,微信deep3321