Fragment嵌套Fragment通过ViewModel进行数据同步的操作
一个场景加上源码的分析
1.场景
一个常见的场景,Fragment中有一个TabView和一个ViewPager构成的界面,Tab上展示ViewPager中Rv的某个数量(例如消息,未读的消息数量),消息的数量接口是在外层的Fragment中请求的,但是当我们在内层的Fragment中点击消息条目时需要修改这个数量,以往的做法是通过事件总线发个通知然后更新等等操作。现在我们既然使用有ViewModel,那个在让内层的Fragment获取外层的ViewModel,然后修改数量后postValue就可以,但是实际的效果和预想不相符,并没有引起数据的刷新。
2.代码分析
修改之前的代码:
private fun handleMessageRead(index: Int) {
mAdapter?.getItem(index)?.isRead = AppConstant.YES
mAdapter?.notifyItemChanged(index)
if (mModel?.type == StatusConfigs.MessageStatus.MESSAGE) {
if (parentFragmentModel == null) {
parentFragmentModel = getFragmentViewModel(MessageModel::class.java)
}
val value = parentFragmentModel!!.unReadCount.value
//在这里获取到value是null
value?.apply {
this.unreadCount -= 1
parentFragmentModel?.unReadCount?.postValue(this)
}
}
}
获取到的value是null,不应该啊,网络请求应该是已经给赋值了。最终看了一下两个parentFragmentModel,发现在两个fragment中获取到的是两个不同的对象。
修改后台的代码:
private fun handleMessageRead(index: Int) {
if (mAdapter?.getItem(index)?.isRead == AppConstant.NO) {
mAdapter?.getItem(index)?.isRead = AppConstant.YES
mAdapter?.notifyItemChanged(index)
if (mModel?.type == StatusConfigs.MessageStatus.MESSAGE) {
if (parentFragmentModel == null) {
parentFragmentModel = ViewModelProvider(
requireActivity(),
ViewModelProvider.NewInstanceFactory()
).get(MessageModel::class.java)
}
val value = parentFragmentModel!!.unReadCount.value
value?.apply {
value.unreadCount -=1
parentFragmentModel?.unReadCount?.postValue(this)
}
}
}
}
这两个区别就是getFragmentViewModel(MessageModel::class.java)是封装在Base里的一个方法来获取对应的ViewModel:
protected open fun <T : ViewModel?> getFragmentViewModel(modelClass: Class<T>): T {
if (mFragmentProvider == null) {
mFragmentProvider = ViewModelProvider(this)
}
return mFragmentProvider!!.get(modelClass)
}
两个不同就是在构建ViewModelProvider是一个传入的是当前的Fragment一个是activity,那么它们为什么会引起不一样的结果?
3.源码分析
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
这里的owner就是我们传入的activity或者fragment,分别看一下它们的getViewModelStore的实现:
activity:
@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
fragment:
@NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
if (viewModelStore == null) {
viewModelStore = new ViewModelStore();
mViewModelStores.put(f.mWho, viewModelStore);
}
return viewModelStore;
}
两者的差别不大,都是如果当前为null的话构建一个ViewModelStore返回,如果有的话直接返回,所以这个ViewModelStore是跟随不同的Fragment和Activity生命周期的,并且所属对象是不同的。
接下来我们再看一下获取ViewModel的get方法:
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
//通过key在ViewModelStore中查找对应的ViewModel
ViewModel viewModel = mViewModelStore.get(key);
//判断获取到的viewModel是不是modelClass的实例对象,如果是的话返回当前的viewModel
if (modelClass.isInstance(viewModel)) {
if (mFactory instanceof OnRequeryFactory) {
((OnRequeryFactory) mFactory).onRequery(viewModel);
}
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
//执行到这说明对应的ViewModel没有找到,那么根据工厂不同来生成ViewModel
if (mFactory instanceof KeyedFactory) {
viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
} else {
viewModel = (mFactory).create(modelClass);
}
//存储以及返回对应的viewModel
mViewModelStore.put(key, viewModel);
return (T) viewModel;
}
到这里我们也就能分析明白为什么在Fragment中传入this和activity的结果不同的原因了,这是因为生成的ViewModel所属的作用域是不同的,this的话只在当前的Fragment中生效,那么FragmentA和FragmentB分别去创建ViewModel它们是不同的,而如果传入的Activity,那么这个ViewModel的作用域是Activity级别的,所有依附于当前Activity中的Fragment通过activity这个owner获取同一个class的ViewModel都会是同一个。