4. 在兄弟 Fragment 之间共享 ViewModel:requireActivity().let { ViewModelProvider(it).get(SharedViewModel::class.java) }

兄弟 Fragment 是指附加到同一 Activity 但彼此没有直接嵌套关系的 Fragment。当两个兄弟 Fragment 需要共享状态(如用户选择、表单数据或实时列表)时,传统做法是通过 Activity 中介回调,接口繁琐且易出错。Android 架构组件提供了更简洁的方案:将 ViewModel 的作用域限定在 Activity 生命周期,使所有附加到该 Activity 的 Fragment 能访问同一个 ViewModel 实例。

4.1 原理:Activity 作用域的 ViewModel

ViewModelProvider 的构造参数决定了 ViewModel 的存储范围。当传入 activity 作为 ViewModelStoreOwner 时,ViewModel 会保存在 Activity 的 ViewModelStore 中,Activity 生命周期结束后才被清除。所有通过 ViewModelProvider(activity).get(SharedViewModel::class.java) 获取的 Fragment 都会得到完全相同的实例,从而实现兄弟 Fragment 间的数据共享。

核心 API 对比:
  • ViewModelProvider(fragment).get(Xxx::class.java) → 作用域为 Fragment 自身,每个 Fragment 独立实例。
  • ViewModelProvider(activity).get(Xxx::class.java) → 作用域为 Activity,所有 Fragment 共享同一实例。

4.2 实现流程

  1. 创建共享 ViewModel 类:继承 ViewModel,包含需要共享的数据(推荐使用 LiveData 或 StateFlow)。
  2. 在 Fragment A 中获取实例:调用 requireActivity().let { ViewModelProvider(it).get(SharedViewModel::class.java) },并读取/写入数据。
  3. 在 Fragment B 中获取实例:使用完全相同的代码获取同一个 ViewModel 实例,并观察数据变化。
  4. 确保 Activity 不提前销毁:Fragment 附加的 Activity 必须在整个交互过程中存活(典型场景:单 Activity 应用)。
  5. 清理工作:当不再需要共享时(例如 Fragment 被移除),ViewModel 会自动保留直到 Activity 销毁,无需手动清理。

4.3 代码示例

SharedViewModel.kt

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

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

FragmentA.kt(发送方)

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

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = requireActivity().let {
            ViewModelProvider(it).get(SharedViewModel::class.java)
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        btnSubmit.setOnClickListener {
            viewModel.selectItem("选项A")
        }
    }
}

FragmentB.kt(接收方)

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

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = requireActivity().let {
            ViewModelProvider(it).get(SharedViewModel::class.java)
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.selectedItem.observe(viewLifecycleOwner) { item ->
            // 更新 UI
            textSelected.text = "已选择: $item"
        }
    }
}

4.4 API 详解

API说明
requireActivity()返回当前 Fragment 附加的 Activity 实例,若 Fragment 未附加则抛出异常。
ViewModelProvider(storeOwner)创建 ViewModelProvider,storeOwner 可以是 Activity、Fragment 或 NavigationBackStackEntry。
.get(ViewModelClass)返回与 storeOwner 关联的 ViewModel 实例。若已存在则复用,否则通过默认构造函数创建。
.let { ... }Kotlin 作用域函数,将对象作为 lambda 接收者,避免重复调用 requireActivity()。

4.5 调试验证

  • 在两个 Fragment 中分别设置断点,观察 viewModel 的 hashCode 是否相同。
  • 使用 adb shell dumpsys activity activities 检查 Activity 的 ViewModelStore 状态。
  • 在 SharedViewModel 中添加 init { Log.d("VM", "Created: $this") },确认只打印一次。

4.6 常见错误

错误 1:在 Fragment 中使用 ViewModelProvider(this)
会导致每个 Fragment 获得独立的 ViewModel 实例,兄弟 Fragment 无法共享数据。
错误 2:在 Fragment 的 onCreateView 或 onViewCreated 中获取 ViewModel 后,未考虑 Fragment 生命周期重建
ViewModel 会在 Activity 重建后继续存在,但 Fragment 重新创建时必须重新绑定观察者。确保在 onCreate 中获取实例,避免重复创建。
错误 3:在 Activity 中使用 requireActivity() 导致空指针
如果在 Fragment 尚未附加到 Activity 时调用(如构造函数中),会抛出 IllegalStateException。应将获取逻辑放在 onAttach 之后。

4.7 性能与稳定性优化

  • 避免在 ViewModel 中持有 Activity/Context 引用:防止内存泄漏。推荐使用 LiveData 或 StateFlow 传递数据。
  • 使用 viewLifecycleOwner 而非 this:在 Fragment 中观察 LiveData 时,使用 viewLifecycleOwner 可确保在视图销毁时自动解绑。
  • 限制共享数据粒度:不要将整个 Activity 或大型 Bitmap 放入 ViewModel,使用轻量级值对象。
  • 考虑 Navigation 组件场景:若使用 Navigation,可考虑将 ViewModel 作用域限定到 NavBackStackEntry,避免所有 Fragment 共享。

4.8 共享流程 SVG 图

兄弟 Fragment 共享 ViewModel 流程 Activity (宿主) SharedViewModel Fragment A Fragment B 关键点: ① 两个 Fragment 使用 requireActivity() 获取同一个 Activity 作为 storeOwner。 ② ViewModelProvider 会返回相同的 SharedViewModel 实例,从而实现双向通信。

4.9 完整使用模板(推荐写法)

// 在基类或工具类中封装,避免重复代码
inline fun <reified VM : ViewModel> Fragment.sharedViewModel(): VM {
    return requireActivity().let {
        ViewModelProvider(it).get(VM::class.java)
    }
}

// 在 Fragment A 中使用
val viewModel: SharedViewModel by lazy { sharedViewModel() }

// 在 Fragment B 中使用(代码完全一致)
val viewModel: SharedViewModel by lazy { sharedViewModel() }
最佳实践:将共享 ViewModel 的获取封装为扩展函数,减少代码重复,并统一管理作用域。若项目使用 Koin 或 Hilt,可进一步将 ViewModel 注入与 Activity 作用域绑定。

通过以上方式,兄弟 Fragment 之间无需接口或 EventBus,即可简洁、可靠地共享状态。此模式尤其适合仪表板、表单分页、选项卡等场景。