Fragment无法观测ViewModel中LiveData变化的问题及解法验证
问题背景
我在NetworkDisadvantageVm中定义了MutableLiveData<Boolean>(isEquipmentTabEnabled),在NetWorkDisadvantageDetailFragment(Fragment1)中调用updateIsEquipmentTab方法更新该LiveData,在NetworkDisadvantageFragment(Fragment2)中对其进行观测。但Fragment1调用更新方法后,Fragment2无法观测到变化,调试发现LiveData的观察者列表为空,且Fragment1处于不可见状态(因ViewPager切换导致)。
我将两个Fragment中获取ViewModel的代码从:
mViewModel = ViewModelProviders.of(this, mViewModelFactory) .get(NetworkDisadvantageVm::class.java)
修改为:
mViewModel = ViewModelProviders.of(requireActivity(), mViewModelFactory) .get(NetworkDisadvantageVm::class.java)
请问这是否是正确的解决方式?
解决方案确认
这个修改是完全正确的,核心原因如下:
原问题本质:ViewModel作用域不匹配
当你使用ViewModelProviders.of(this)时,this指向当前Fragment实例,这意味着每个Fragment都会创建并持有独立的ViewModel实例。Fragment1更新的是自己ViewModel里的isEquipmentTabEnabled,而Fragment2观测的是另一个完全独立的ViewModel实例中的LiveData,自然无法收到更新通知——这也是你看到观察者列表为空的原因:Fragment2的ViewModel里的LiveData根本没有被Fragment1关联的观察者订阅。
改为requireActivity()后,ViewModel的作用域变为Activity级别,两个Fragment会共享同一个NetworkDisadvantageVm实例。此时Fragment1更新的LiveData和Fragment2观测的是同一个对象,LiveData的变化就能正确传递到Fragment2的观察者中。
额外优化建议
优化LiveData的暴露方式
为了保证数据的单向流动,避免外部直接修改LiveData,建议在ViewModel中将MutableLiveData私有化,对外暴露不可变的LiveData:private val _isEquipmentTabEnabled = MutableLiveData<Boolean>(false) val isEquipmentTabEnabled: LiveData<Boolean> get() = _isEquipmentTabEnabled fun updateIsEquipmentTab(condition: Boolean) { _isEquipmentTabEnabled.value = condition }更精准的ViewModel作用域(可选)
如果两个Fragment的生命周期和Activity不完全绑定(比如Fragment2是DialogFragment,关闭后不需要保留ViewModel数据),可以将ViewModel的作用域绑定到Fragment2(父Fragment),Fragment1通过requireParentFragment()获取ViewModel:- Fragment2中获取ViewModel:
mViewModel = ViewModelProviders.of(this, mViewModelFactory) .get(NetworkDisadvantageVm::class.java) - Fragment1中获取ViewModel:
mNetWorkDisadvantageViewModel = ViewModelProviders.of(requireParentFragment(), mViewModelFactory) .get(NetworkDisadvantageVm::class.java)
这样ViewModel的生命周期会和Fragment2绑定,比Activity级别更精准。
- Fragment2中获取ViewModel:
修复ViewPager索引越界问题
在Fragment2的onCreateView中,你设置了binding.viewPager.currentItem = 2,但你的Fragment列表frgs只有2个元素(索引0和1),这会导致索引越界异常,建议修改为currentItem = 0或currentItem = 1。
相关代码(修正格式后)
ViewModel代码
class NetworkDisadvantageVm @Inject constructor( var applicationn: Application, var networkDisadvantageManager: NetworkDisadvantageManager, var goodsItemManager: GoodsItemManager, var gson: Gson ) : BaseVm(applicationn) { private var isEquipmentTabEnabled = MutableLiveData<Boolean>(false) val getIsEquipmentTabEnabled: MutableLiveData<Boolean> get() = isEquipmentTabEnabled fun updateIsEquipmentTab(condition: Boolean) { isEquipmentTabEnabled.value = condition } override fun handleUserAction(action: String, data: Any) { when (action) { } } companion object { const val ACTION_UPDATE_NETWORK_DISADVANAGE = "ACTION_UPDATE_NETWORK_DISADVANAGE" const val ACTION_ERROR_UPDATE_NETWORK_DISADVANAGE = "ACTION_ERROR_UPDATE_NETWORK_DISADVANAGE" } }
Fragment1(NetWorkDisadvantageDetailFragment)代码
class NetWorkDisadvantageDetailFragment : BaseFragment() { private var _binding: NabNetworkDisadvantageFragmentBinding? = null private lateinit var mNetWorkDisadvantageViewModel: NetworkDisadvantageVm private lateinit var mission: Mission override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mNetWorkDisadvantageViewModel = ViewModelProviders.of(this, mViewModelFactory) .get(NetworkDisadvantageVm::class.java) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = NabNetworkDisadvantageFragmentBinding.inflate(inflater, container, false) val view = binding.root binding.datetext.text = GeneralUtil.convertPersianDate(GeneralUtil.Now()) binding.datetext.tag = GeneralUtil.getDateFormat().format(GeneralUtil.Now()) binding.takenAction.setOnClickListener { onRadioButtonClicked(it) } binding.inProgressReferTo.setOnClickListener { onRadioButtonClicked(it) } binding.tempActionReferTo.setOnClickListener { onRadioButtonClicked(it) } binding.CustomerAbsence.setOnClickListener { onRadioButtonClicked(it) } binding.notSeen.setOnClickListener { onRadioButtonClicked(it) } binding.wrongAddressNoActionTaken.setOnClickListener { onRadioButtonClicked(it) } return view } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) mNetWorkDisadvantageViewModel.getIsEquipmentTabEnabled.observe(viewLifecycleOwner) { isEnabled -> Log.e("test","tet") } } private val binding get() = _binding!! private fun onRadioButtonClicked(view: View) { val checked = (view as RadioButton).isChecked when (view.id) { binding.takenAction.id -> { if (checked) { mNetWorkDisadvantageViewModel.updateIsEquipmentTab(true) binding.inProgressReferTo.isChecked = false } } binding.inProgressReferTo.id -> { if (checked) { mNetWorkDisadvantageViewModel.updateIsEquipmentTab(false) binding.notSeen.isChecked = false } } binding.tempActionReferTo.id -> { if (checked) { mNetWorkDisadvantageViewModel.updateIsEquipmentTab(false) binding.inProgressReferTo.isChecked = false } } binding.CustomerAbsence.id -> { if (checked) { mNetWorkDisadvantageViewModel.updateIsEquipmentTab(false) binding.takenAction.isChecked = false } } binding.notSeen.id -> { if (checked) { mNetWorkDisadvantageViewModel.updateIsEquipmentTab(false) binding.inProgressReferTo.isChecked = false } } binding.wrongAddressNoActionTaken.id -> { if (checked) { mNetWorkDisadvantageViewModel.updateIsEquipmentTab(false) binding.takenAction.isChecked = false binding.tempActionReferTo.isChecked = false binding.inProgressReferTo.isChecked = false binding.CustomerAbsence.isChecked = false binding.notSeen.isChecked = false } } } } companion object { fun newInstance(mission: Mission) = NetWorkDisadvantageDetailFragment().apply { this.mission = mission } } }
Fragment2(NetworkDisadvantageFragment)代码
class NetworkDisadvantageFragment: BaseFullScreenDialogFragment() { private lateinit var mViewModel: NetworkDisadvantageVm private var _binding: NabTabLayoutBinding? = null private lateinit var mission: Mission override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mViewModel = ViewModelProviders.of(this, mViewModelFactory) .get(NetworkDisadvantageVm::class.java) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = NabTabLayoutBinding.inflate(inflater, container, false) val view = binding.root val networkDisadvantage= NetWorkDisadvantageDetailFragment.newInstance(mission) val goodsItem= GoodsItemFragment.newInstance(mission) val title = listOf("اجناس", "فرم اقدام") val frgs = listOf(goodsItem, networkDisadvantage) val adaptor= FragmentAdaptor(requireActivity(), frgs as List<Fragment>, title) binding.viewPager.adapter = adaptor // 修复索引越界问题,改为0或1 binding.viewPager.currentItem = 1 TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position -> tab.text = title[position] }.attach() return view } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) mViewModel.getIsEquipmentTabEnabled.observe(viewLifecycleOwner) { isEnabled -> val tabStrip = binding.tabLayout.getChildAt(0) as LinearLayout val tabView = tabStrip.getChildAt(0) tabView.isEnabled = isEnabled tabView.isClickable = isEnabled } } private val binding get() = _binding!! companion object{ fun newInstance(mission: Mission) = NetworkDisadvantageFragment().apply { this.mission = mission } } }
ViewModel提供者代码
package com.edsab.takrim.injection.component @Subcomponent interface ViewModelSubComponent { @Subcomponent.Builder interface Builder { fun build(): ViewModelSubComponent } fun NetworkDisadvantageVm(): NetworkDisadvantageVm fun NetWorkDisadvantageDetailVm(): NetWorkDisadvantageDetailVm } @Singleton class ViewModelFactory @Inject constructor(viewModelSubComponent: ViewModelSubComponent) : ViewModelProvider.Factory { private val creators: ArrayMap<Class<*>, Callable<out ViewModel>> = ArrayMap() init { creators[NetworkDisadvantageVm::class.java] = Callable { viewModelSubComponent.NetworkDisadvantageVm() } creators[NetWorkDisadvantageDetailVm::class.java] = Callable { viewModelSubComponent.NetWorkDisadvantageDetailVm() } } override fun <T : ViewModel> create(modelClass: Class<T>): T { var creator = creators.get(modelClass) if (creator == null) { creators.forEach { entry -> if (modelClass.isAssignableFrom(entry.key)) { creator = entry.value } } } if (creator == null) { throw IllegalArgumentException("Unknown model class $modelClass") } try { return creator?.call() as T } catch (e: Exception) { throw RuntimeException(e) } } }
内容的提问来源于stack exchange,提问作者MojtabaValizadeh

