Fragment和Activity最佳通信方式 --- 共享ViewModel
背景
在日常开发中,我们经常会遇到Activity和Fragment之间通信的问题,其中之前最简单的办法是通过接口回调,因为fragment在attach时会拿到activity实例,在activity内部能拿到fragment实例,只需要定义接口让activity实现接口即可,但是这样一来不免接口定义的很多,如果逻辑比较复杂,不利于后期维护。
关于其他方案,比如eventBus都可以实现,但是如果你使用MVVM和Jetpack开发的话,这个问题就特别容易解决了,那就是使用共享ViewModel。
使用
话不多说,直接开整。
原理
ViewModel大家应该都很熟悉了,在MVVM架构中充当着保存数据以及逻辑操作的角色,最重要的是它在配置发生改变时依旧会保存数据而且具有恰当的生命周期,一般就是Activity或者Fragment都对应着一个ViewModel。
所以原理也非常简单,既然Activity生命周期比Fragment长,所以Activity的ViewModel的生命周期也比Fragment的ViewModel长,这里直接在Fragment中使用Activity的ViewModel实例即可,当然这里肯定是一个实例,关于为什么ViewModel能保证生命周期安全以及单例,可以查看文章:
有具体解答,就不再赘述。
这里的ViewModel也就相当于是一个activity范围的容器,当然可以实现在activity和fragment以及内部fragment之间的通信了。
具体实现
首先是Activity的代码,这里有3个子Fragment,然后定义了2个ViewModel,其中一个是它自己的,一个是共享的:
@AndroidEntryPoint
class ShellMainActivity : BaseVMActivity<ShellMainViewModel>() {
//这个不管,这个是Activity自己的ViewModel,不需要共享的逻辑
private val shellMainViewModel: ShellMainViewModel by viewModels()
//这个是共享ViewModel
private val sharedViewModel: ShellMainSharedViewModel by viewModels()
//依次增加3个Fragment
private val mShellMainFragment by lazy { ShellMainFragment() }
private val mShellMainPluginFragment by lazy { ShellMainPluginFragment() }
private val mShellMainSettingFragment by lazy { ShellMainSettingFragment() }
override fun getLayoutResId(): Int = R.layout.activity_main
override fun initVM(): ShellMainViewModel {
return shellMainViewModel
}
override fun initView() {
//初始化fragment
val shellMainViewPager2Adapter = ShellMainAdapter(this
, arrayListOf(mShellMainFragment
,mShellMainPluginFragment
,mShellMainSettingFragment))
shellMainViewPager2.adapter = shellMainViewPager2Adapter
ViewPager2Delegate.install(shellMainViewPager2,shellMainTabLayout)
}
override fun initData() {
//对共享fragment里的值进行观察,同时弹出toast
sharedViewModel.testLiveData.observe(this,{
Toast.makeText(this, "$it", Toast.LENGTH_SHORT).show()
})
}
override fun startObserve() {
}
//分别添加3个子fragment
inner class ShellMainAdapter(activity: FragmentActivity, private val fragmentList: List<Fragment>)
: FragmentStateAdapter(activity){
override fun getItemCount(): Int {
return fragmentList.size
}
override fun createFragment(position: Int): Fragment {
return fragmentList[position]
}
}
}
看一下共享ViewModel代码:
@HiltViewModel
class ShellMainSharedViewModel @Inject constructor() : BaseViewModel() {
val testLiveData = MutableLiveData<String>("00")
fun setValue(view: View){
val random = (1 .. 100).random().toString()
Log.i(TAG, "setValue: 随机数 $random")
testLiveData.value = random
}
}
这里非常简单,就是一个LiveData,然后接着看一下Fragment,
这里的注意点是Fragment自己的ViewModel使用viewModels来获取,对于要和整个activity生命周期共享的ViewModel使用activityViewModels来获取:
@AndroidEntryPoint
class ShellMainFragment : BaseVMFragment<ShellMainFragmentViewModel>() {
private val shellMainFragmentViewModel: ShellMainFragmentViewModel by viewModels()
//获取共享ViewModel
private val shellMainSharedViewModel: ShellMainSharedViewModel by activityViewModels()
override fun getLayoutResId(): Int = R.layout.fragment_shell_main
override fun initVM(): ShellMainFragmentViewModel {
return shellMainFragmentViewModel
}
//依次绑定2个viewModel
override fun initView() {
mBinding.setVariable(BR.sharedViewModel,shellMainSharedViewModel)
mBinding.setVariable(BR.viewModel,shellMainFragmentViewModel)
}
override fun initData() {
}
override fun startObserve() {
}
}
然后在xml中,有一个textView控件可以显示viewModel中的值,以及修改值:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<data>
<variable
name="viewModel"
type="com.wayeal.yunapp.shell.mvvm.main.ShellMainFragmentViewModel" />
<variable
name="sharedViewModel"
type="com.wayeal.yunapp.shell.mvvm.main.ShellMainSharedViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".shell.mvvm.main.ShellMainFragment"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="首页" />
<TextView
android:id="@+id/test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{sharedViewModel.testLiveData}"
android:textSize="20sp"
android:textColor="@color/black"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="随机数"
android:onClick="@{sharedViewModel::setValue}"
/>
</LinearLayout>
</layout>
然后其他2个fragment可以一样使用这个共享ViewModel,这样不论是activity还是fragment之间都是用的同一个ViewModel实例,而且还是MVVM的数据驱动模式,可以在达到通信的同时不会造成内存泄漏。
最后我们能看到效果就是下图所示,2个fragment和activity可以无障碍通信:
总结
其实除了Activity和Fragment之间的通信,我还想过是否可以搞个ViewModel在Activity之间通信呢,但是这种思想很快就被否决了,因为官方就不建议这样干,管理起来很麻烦,所以要想实现更大范围内的数据共享,建议在Repository层进行控制,通过Hilt规定Scope都可以实现,就不要搞一些奇奇怪怪的ViewModel了。