Kotlin中MVVM+LiveData实现子Fragment访问父Fragment数据遇Hilt崩溃
问题背景
ListFragment点击列表项后,通过Bundle将数据传递到父Fragment;父Fragment通过ViewPager2包含Detail、Second两个子Fragment,需要让DetailFragment获取该数据展示。尝试通过by viewModels({requireParentFragment()})获取父Fragment的ViewModel时崩溃,报错:
Fragment DetailsFragment is not a child Fragment, it is directly attached to dagger.hilt.android.internal.managers
错误原因
ViewPager2若默认使用Activity的FragmentManager管理子Fragment,会导致子Fragment的父容器不是目标父Fragment,而是与Hilt的管理组件绑定,因此无法通过requireParentFragment()获取到预期的父Fragment实例。
解决方案
方案一:通过共享父Fragment的ViewModel(推荐)
核心是让ViewPager2的子Fragment归属于父Fragment的childFragmentManager管理,这样就能正常获取父Fragment的ViewModel。
父Fragment中正确配置ViewPager2的Adapter
创建FragmentStateAdapter时传入父Fragment实例(内部会自动使用父Fragment的childFragmentManager):class ParentPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) { override fun getItemCount(): Int = 2 override fun createFragment(position: Int): Fragment { return when(position) { 0 -> DetailFragment() 1 -> SecondFragment() else -> throw IllegalArgumentException("Invalid position") } } } // 父Fragment的onViewCreated方法中初始化ViewPager2 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val viewPager = view.findViewById<ViewPager2>(R.id.view_pager) viewPager.adapter = ParentPagerAdapter(this) // 绑定TabLayout与ViewPager2的代码(略) }父ViewModel中存储共享数据
在MyListsViewModel中添加LiveData用于存储和分发数据:class MyListsViewModel : ViewModel() { private val _selectedList = MutableLiveData<MyListsResponse?>() val selectedList: LiveData<MyListsResponse?> = _selectedList fun setSelectedList(data: MyListsResponse) { _selectedList.value = data } }父Fragment接收数据并传入ViewModel
// 父Fragment中解析Bundle数据并传递给ViewModel val jsonMyLists = arguments?.getString("mylist") jsonMyLists?.let { val mylists = Gson().fromJson<MyListsResponse>(it, MyListsResponse::class.java) mylistsViewModel.setSelectedList(mylists) }DetailFragment获取父ViewModel并观察数据
class DetailFragment : Fragment() { // 通过父Fragment作用域获取ViewModel private val parentViewModel: MyListsViewModel by viewModels({ requireParentFragment() }) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 观察数据更新UI parentViewModel.selectedList.observe(viewLifecycleOwner) { data -> data?.let { // 用数据渲染UI } } } }
方案二:直接给子Fragment传递Bundle
如果不想依赖ViewModel共享,可在父Fragment创建子Fragment时,将数据直接传入子Fragment的arguments中。
父Fragment创建Adapter时传入数据
class ParentPagerAdapter( fragment: Fragment, private val listData: MyListsResponse? ) : FragmentStateAdapter(fragment) { override fun getItemCount(): Int = 2 override fun createFragment(position: Int): Fragment { return when(position) { 0 -> DetailFragment().apply { arguments = Bundle().apply { putString("mylist", Gson().toJson(listData)) } } 1 -> SecondFragment() else -> throw IllegalArgumentException("Invalid position") } } } // 父Fragment初始化Adapter val mylistsData = ... // 解析后的MyListsResponse实例 viewPager.adapter = ParentPagerAdapter(this, mylistsData)DetailFragment解析arguments获取数据
class DetailFragment : Fragment() { private var mylists: MyListsResponse? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.getString("mylist")?.let { json -> mylists = Gson().fromJson(json, MyListsResponse::class.java) } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) mylists?.let { // 用数据展示UI } } }
内容的提问来源于stack exchange,提问作者Beta_Dev

