Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android navigation component with shared view models

A viewmodel lives and dies with an activity or the fragment it is attached to. This has certain ramifications that it is beyond me why isn't anyone asking about (if we get the Navigation architecture into the picture).

According to the latest android blogs and the way navigation framework works , we are recommended to go in the Single Activity Multiple Fragments verse.

Supposedly I have the following app design .

Activity A (Application Entry Point) ---------- Fragment (A) (Uses ViewModel AB) Fragment (B) (Uses ViewModel AB) Fragment (C) (Uses ViewModel CDE) Fragment (D) (Uses ViewModel CDE) Fragment (E) (Uses ViewModel CDE) 

Now since I use shared viewmodels that means my viewmodels would be attached to the activity. However this appears to be leaky. Like if I have traversed all the way from A to E and now come back popping off fragments to fragment B , the viewmodel CDE should be destroyed , but it wont be since it is connected to the activity.

Also we cannot connect our viewmodels to the fragment since we are going to be sharing their data.

The fact that only I am raising this question makes me believe i am at mistake here with my understanding. Would be elated if I could be given a proper insight into the situation.

like image 303
Muhammad Ahmed AbuTalib Avatar asked Mar 13 '19 08:03

Muhammad Ahmed AbuTalib


People also ask

Can a fragment have multiple ViewModel?

In fact you can have multiple view models for a single fragments doing different things for you.

Can we use same ViewModel for different activities?

But you can achieve this by passing a single instance of a custom ViewModel factory which acts as a singleton factory, so it will always pass the same instance of your ViewModel among different activities.

What is navGraphViewModels?

navGraphViewModels is a kotlin extension function for fragments that creates nav graph scoped view models. It accepts two parameters: a nav graph ID and also a viewmodel factory. You can simply write: Val order selection by navGraphViewModels(R. id.


2 Answers

This is really a problem and has been reported to Google.

Fortunately since Navigation 2.1.0-alpha02 (stable in 2.1.0) this problem has been solved. You can find the change log here and the document.

You can now create ViewModels that are scoped at a navigation graph level via the by navGraphViewModels() property delegate for Kotlin users or by using the getViewModelStore() API added to NavController.

First you should select some of fragments in your nav graph designer, then right click on them and choice Move to Nested Graph to create a new graph which will be used as a 'scope' like this:

class DetailFr : Fragment() {     private val vm: DetailViewModel by navGraphViewModels(R.id.main_nav_graph) } 

You can learn more about Nested Graph here.

like image 148
Chenhe Avatar answered Oct 08 '22 03:10

Chenhe


Since Navigation 2.1.0-alpha02 (stable in 2.1.0), you can create ViewModels with a scope at a level of navigation graph through by navGraphViewModels().

To get a ViewModel not to be attached to an activity or a single fragment, you have to create a nested navigation graph and request instances of the ViewModel in the scope of that graph. This will cause that while you are inside the nested navigation graph, ViewModel will live and the fragments inside the nested graph will reuse the same instance of the ViewModel.

In this way, you can have several nested navigation graphs, each with a single instance of ViewModel that will be shared among the fragments that make up that graph.

I will follow your same distribution of fragments and ViewModels:

MainActivity (Application Entry Point) ---------- Fragment (A) (Uses SharedViewModelOne) -> navGraphOne Fragment (B) (Uses SharedViewModelOne) -> navGraphOne Fragment (C) (Uses SharedViewModelTwo) -> navGraphTwo Fragment (D) (Uses SharedViewModelTwo) -> navGraphTwo 

To achieve this you must follow these steps:

  1. Your build.gradle(Module) should look like this

    ... apply plugin: 'kotlin-kapt'  android {     ...     kotlinOptions {         jvmTarget = "1.8"     } }  dependencies{     ...     implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'     kapt 'androidx.lifecycle:lifecycle-compiler:2.2.0'     implementation 'androidx.navigation:navigation-fragment-ktx:2.2.1'     implementation 'androidx.navigation:navigation-ui-ktx:2.2.1' } 
  2. Select the fragments that will share the same ViewModel and add them to a nested navigation graph. To do this, select the fragments in your navigation graph designer, then right click on them and choose Move to Nested Graph

    In this example I added FragmentA and Fragment B to navGraphOne and FragmentC and
    Fragment D to navGraphTwo.

    Find more information about Nested Navigation Graph here

  3. In Fragment A and Fragment B, request an instance of SharedViewModelOne.

    private val modelOne: SharedViewModelOne by navGraphViewModels(R.id.navGraphOne) {     //defaultViewModelProviderFactory or the ViewModelProvider.Factory you are using.     defaultViewModelProviderFactory }  override fun onCreateView(     .. ): View? {     ...     //use binding.lifecycleOwner = viewLifecycleOwner     //to make sure the observer disappears when the fragment is destroyed     modelOne.item.observe(viewLifecycleOwner, Observer {         //do Something     })     ... } 
  4. In Fragment C and Fragment D, request an instance of SharedViewModelTwo.

    private val modelTwo: SharedViewModelTwo by navGraphViewModels(R.id.navGraphTwo) {     //defaultViewModelProviderFactory or the ViewModelProvider.Factory you are using.     defaultViewModelProviderFactory }  override fun onCreateView(     .. ): View? {     ...     //use binding.lifecycleOwner = viewLifecycleOwner     //to make sure the observer disappears when the fragment is destroyed     modelTwo.item.observe(viewLifecycleOwner, Observer {         //do Something     })     ... } 
  5. Then to verify that only a single instance of the ViewModels is created and that it is shared among the fragments, override the onCleared() method and add a checkpoint in the init{} of the ViewModel.

    For Example:

    class SharedViewModelOne : ViewModel() {      private val _item = MutableLiveData<String>()     val item : LiveData<String>         get() = _item      init {         Log.d(TAG, "SharedViewModelOne has created!")     }      override fun onCleared() {         super.onCleared()         Log.d(TAG, "SharedViewModelOne has removed!")     } } 

After having followed the previous steps, you should be able to create a ViewModel that will be shared among the fragments that belong to the same nested navigation graph said ViewModel will only live while you are inside the graph, if you leave it, it will be destroyed.

If you feel that something is not very clear to you, you can review this repo and clarify your doubts.

like image 43
Alejandro Fort Avatar answered Oct 08 '22 02:10

Alejandro Fort