background

In daily development, we often encounter communication problems between activities and fragments. Previously, the simplest way is to call back through the interface, because the Fragment will get the Activity instance when attaching, and the Fragment instance can be got inside the Activity. You only need to define the interface for the activity to implement the interface, but this will inevitably define a lot of interface, if the logic is more complex, not conducive to later maintenance.

Other solutions, such as eventBus, can be implemented, but if you’re developing with MVVM and Jetpack, the problem is especially easy to solve by using a shared ViewModel.

use

Don’t say a word. Just turn it on.

The principle of

Viewmodels are familiar with the MVVM architecture, which plays a role in saving data and logical operations. Most importantly, they can still save data when configuration changes and have a proper life cycle. Generally, activities or fragments have a ViewModel corresponding to them.

The principle is very simple. Since the Activity lifecycle is longer than the Fragment’s ViewModel, the Activity’s ViewModel lifecycle is longer than the Fragment’s ViewModel. Use a ViewModel instance of an Activity in your Fragment. For more information on why viewModels are life-cycle safe and singletons, check out this article:

Juejin. Cn/post / 699694…

There are specific solutions, I will not repeat them.

The ViewModel is essentially an activity-scoped container that can communicate between activities and fragments as well as within them.

The specific implementation

First, the code for the Activity has three sub-fragments. Then, we define two viewModels, one of which is its own and one shared:

@AndroidEntryPoint class ShellMainActivity : BaseVMActivity<ShellMainViewModel>() {BaseVMActivity<ShellMainViewModel>() {BaseVMActivity<ShellMainViewModel>() { Private val sharedViewModel: private val sharedViewModel: 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 () {/ / initializes the fragments val shellMainViewPager2Adapter = ShellMainAdapter(this , arrayListOf(mShellMainFragment ,mShellMainPluginFragment ,mShellMainSettingFragment)) shellMainViewPager2.adapter = shellMainViewPager2Adapter ViewPager2Delegate.install(shellMainViewPager2,shellMainTabLayout) } override fun initData() {// Observe the values in the shared fragment, And pop up the toast sharedViewModel. TestLiveData. Observe (this, {toast. MakeText (this, "$" it, Toast.length_short).show()})} Override Fun startObserve() {} add 3 sub-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] } } }Copy the code

Look at the shared ViewModel code:

@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 number $random") testLiveData.value = random}}Copy the code

It’s very simple, just a LiveData, and then looking at the Fragment,

Use viewModels to retrieve the Fragment’s own ViewModel, and use activityViewModels to retrieve the Fragment’s ViewModel to be shared with the activity lifecycle:

@AndroidEntryPoint class ShellMainFragment : BaseVMFragment<ShellMainFragmentViewModel>() { private val shellMainFragmentViewModel: ShellMainFragmentViewModel by viewModels () / / access Shared ViewModel private val shellMainSharedViewModel: ShellMainSharedViewModel by activityViewModels() override fun getLayoutResId(): Int = R.layout.fragment_shell_main override fun initVM(): ShellMainFragmentViewModel {return ShellMainFragmentViewModel} / / in turn bind two viewModel override fun initView () { mBinding.setVariable(BR.sharedViewModel,shellMainSharedViewModel) mBinding.setVariable(BR.viewModel,shellMainFragmentViewModel) } override fun initData() { } override fun startObserve() {}}Copy the code

Then in XML, there is a textView control that displays the values in the viewModel and modifies them:

<? The 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=" home "/> <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=" random number" android:onClick="@{sharedViewModel::setValue}" /> </LinearLayout> </layout>Copy the code

Then the other two fragments can use the same shared ViewModel, so that both activities and fragments use the same ViewModel instance, and the MVVM data-driven mode can communicate without causing memory leaks.

In the end, you can see the result as shown in the following figure. The two fragments and the activity can communicate with each other barrier-free:

conclusion

In fact, in addition to the communication between activities and fragments, I also thought about whether it was possible to create a ViewModel to communicate between activities. However, this idea was rejected soon, because it was officially not recommended to do so and it was very troublesome to manage. Therefore, in order to achieve a wider range of data sharing, It is recommended that you do this in the Repository layer, with Hilt specifying that Scope can be implemented, rather than having some fancy ViewModel.