android Jetpack - ViewModel usage method and detailed principle analysis

Posted by marseille on Sat, 29 Jan 2022 10:29:19 +0100

My official account, "Ann Ann Ann Android", is free to learn knowledge.

Most people are more concerned about usage, so I'll talk about usage first, then the understanding of viewmodel, and finally the source code

1. ViewModel initialization mode

When I come to Android x, the creation method of Viewmodel is very different from the old version, so I still want to talk about the initialization of Viewmodel here

1.1. Android factory initialization

The model is recreated every time and is not controlled by ViewModelStore, so this method is prohibited without special requirements

Created using the AndroidViewModelFactory factory

  1. viewmodel class definition
class AndroidFactoryViewModel(app:Application): AndroidViewModel(app) {
    fun print(){
        logEE("Create using Android default factory viewmodel")
    }
}
  1. Create viewmodel instance code
val model = ViewModelProvider.AndroidViewModelFactory.getInstance(application)
                .create(AndroidFactoryViewModel::class.java)
            model.print()

1.2. Simple factory initialization ViewModel

The model is recreated every time and is not controlled by ViewModelStore, so this method is prohibited without special requirements

NewInstanceFactory

  1. viewmodel class definition code
class SimpleFactoryViewModel: ViewModel() {
    fun print(){
        logEE("Create with simple factory viewmodel")
    }
}
  1. Create viewmodel code
 val model =ViewModelProvider.NewInstanceFactory().create(SimpleFactoryViewModel::class.java)
 model.print()

1.3. Customize Android factory initialization

Multiple creation can reuse the model without re creation

The default factory can only create instances of ViewModel with Application. We can customize the construction parameters through the custom factory

  1. Define Android factory
class CustomAndroidViewModelFactory(val app: Application, private val data: String) :
    ViewModelProvider.AndroidViewModelFactory(app) {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (AndroidViewModel::class.java.isAssignableFrom(modelClass)) {
            return try {
                modelClass.getConstructor(Application::class.java, String::class.java)
                    .newInstance(app, data)
            } ...
        }
        return super.create(modelClass)
    }
}

The code in catch is omitted. You can check it in the source code

In the construction of our custom factory, a custom data parameter is passed in to represent our custom construction parameters

  1. viewmodel class definition

    You must inherit AndroidViewModel

class CustomAndroidViewModel(private val app: Application, private val data: String) :
    AndroidViewModel(app) {
    fun print() {
        logEE(data)
    }
}

Our CustomAndroidViewModel class also has a data: String parameter, which corresponds to the data parameter in the custom factory in the previous step

  1. Instantiate viewmodel
val model = ViewModelProvider(
                viewModelStore,
                CustomAndroidViewModelFactory(application, "Custom Android factory creation viewmodel")
            ).get(CustomAndroidViewModel::class.java)
            model.print()

1.4. User defined simple factory initialization

Multiple acquisition can reuse the model without re creation

The custom simple factory is also used to realize the purpose of transferring parameters to the viewmodel structure. Without much nonsense, go directly to the code

  1. Define Android factory
class CustomSimpleViewModelFactory(app:Application,private val data:String) : ViewModelProvider.AndroidViewModelFactory(app) {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return try {
            modelClass.getConstructor(String::class.java).newInstance(data)
        } ......
    }
}
  1. viewmodel class definition
class CustomSimpleViewModel(private val data: String) : ViewModel() {
    fun print() {
        logEE(data)
    }
}
  1. Instantiate viewmodel
val model = ViewModelProvider(
                viewModelStore,
                CustomSimpleViewModelFactory(application, "Custom simple factory creation viewmodel")
            ).get(CustomSimpleViewModel::class.java)
            model.print()

1.5 create viewmodel using delegation mechanism (recommended)

Multiple creation can reuse the model without re creation

google officially provided us with the activity KTX library, through which we can use the delegation mechanism to create the viewmodel

The implementation method is as follows:

  1. Join dependency
implementation 'androidx.activity:activity-ktx:1.2.2'
  1. Create viewmodel class
class EnTrustModel : ViewModel() {
    fun print(){
        logEE("Official account \"Android\" Free learning")
    }
}
  1. Instantiate viewmodel
private val wtModel: EnTrustModel by viewModels<EnTrustModel> {
        ViewModelProvider.NewInstanceFactory()
    }
  1. Call the method of viewmodel
 wtModel.print()

2. viewmodel overview

The ViewModel class is designed to store and manage interface related data in a lifecycle oriented manner. The ViewModel class allows data to remain after configuration changes such as screen rotation.

We know that both Activity and Fragment in android can manage the life cycle. If the Activity/Fragment is destroyed by the system, such as screen rotation, the Activity/Fragment must be re created. At this time, there is a problem of data recovery. We usually use onSaveInstanceState and onRestoreSaveInstance to save and restore data, but these two methods can only deal with small amounts of data.

In case of large amount of data, viewmodel is a good choice

At the same time, viewmodel can also help us separate business logic from Activity/Fragment

3. viewmodel realizes data sharing between fragments

It is very common for multiple fragments to appear in the same activity, and these fragments need to communicate with each other

In the past, we may share data in the following ways:

  1. EventBus
  2. Interface callback
  3. Global shared data pool

However, after learning viewmodel, we will find that viewmodel can easily realize data sharing between fragments

We can make multiple fragments share the same ViewModel to share data.

In this example, we want to realize the following functions:

There are two fragments in activity, FragmentA and FragmentB. We start the countdown of 2000s in the construction method of ViewModel, observe the countdown data in FragmentA and display it on the page. Then switch to FragmentB. If the countdown seconds in FragmentB are not reset to 2000, our data sharing is successful.

Upper Code:

  1. Create ViewModel
class ShareDataModel: ViewModel() {
    val liveData = MutableLiveData<String>()
    var total = 2000L
    init {
        /**
         * Realize the countdown, counting down once a second
         */
        val countDownTimer = object :CountDownTimer(1000 * total, 1000) {
            override fun onTick(millisUntilFinished: Long) {
                liveData.postValue("Countdown time remaining ${--total}")
            }
            override fun onFinish() {
                logEE("Countdown complete")
            }
        }
        countDownTimer.start()
    }
}
  1. Create FragmentA
class FragmentA : Fragment() {
    private val model: ShareDataModel by activityViewModels()
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_a, container, false).apply {
            model.liveData.observe(viewLifecycleOwner,
                { value -> findViewById<TextView>(R.id.tv_aresult).text = value })
        }
    }
}
  1. Create FragmentB
class FragmentB : Fragment() {
    private val model: ShareDataModel by activityViewModels()
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_b, container, false).apply {
            model.liveData.observe(viewLifecycleOwner,
                { value -> findViewById<TextView>(R.id.tv_bresult).text = value })
        }
    }
}
  1. Show results

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-dyv3swxq-1624095191487)( https://files.mdnice.com/user/15648/d3b75b42-2c0a-4040-8493-72bcc62e0f41.gif )]

Through gif, we found that even after replace ment, the countdown data still did not change, indicating that we successfully realized data sharing

4. Some knowledge points about viewmodel

  1. viewmodel must not reference views
  2. The onCreate method may be used many times during the existence of an activity, but our viewmodel will be recycled only when the activity ends and is destroyed
  3. viewmodel observes the life cycle of activity/fragment through life cycle

5. Source code analysis

5.1. Create ViewModel source code analysis

Let's first look at the code for creating ViewModel:

val model = ViewModelProvider(
                viewModelStore,
                CustomSimpleViewModelFactory(application, "Custom simple factory creation viewmodel")
            ).get(CustomSimpleViewModel::class.java)

Let's start with the ViewModelProvider constructor and get method,

  1. ViewModelProvider constructor

The ViewModelProvider constructor passes in two parameters, ViewModelStore (used to store and manage viewmodels) and Factory (the Factory that creates viewmodels)

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }
  1. get method of ViewModelProvider
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);
    }

The getCanonicalName method can obtain a string that can uniquely identify the Class and will eventually be used as the key to generate the ViewModel stored in our ViewModelStore.

After obtaining the key, we call the overloaded get method with the key and modelClass as parameters

  1. Overload get method

The interpretation of the method is written in the code

 public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);//Get the ViewModel from the ViewModelStore according to the key. If there is one, it will return

        if (modelClass.isInstance(viewModel)) {//Judge whether the obtained ViewModel is the passed in modelClass type
            if (mFactory instanceof OnRequeryFactory) {//Don't look here
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;//If yes, return viewmodel
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) mFactory).create(key, modelClass);//I don't usually use this factory
        } else {
            viewModel = mFactory.create(modelClass);//Create ViewModel
        }
        mViewModelStore.put(key, viewModel);//Add ViewModel to mViewModelStore
        return (T) viewModel;
    }

5.2. ViewModelStore class parsing

There are three important methods get, put and clear in ViewModelStore, and a HashMap < string, ViewModel > is maintained

  1. get
 final ViewModel get(String key) {
        return mMap.get(key);
    }
It's simple. It's based on key obtain ViewModel
  1. put
 final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);//oldViewModel refers to the overridden ViewModel
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }

Add a ViewModel to the map and clean up the overwritten ViewModel data (because only one ViewModel of the same type can exist in the same activity)

  1. clear

The next section will talk about

Call timing of clear method (key points)

Let's take a look at the clear method first

public final void clear() {
      for (ViewModel vm : mMap.values()) {
          vm.clear();
      }
      mMap.clear();
  }

The call position of clear method is in the construction method of ComponentActivity. The code is as follows:

getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    // Clear out the available context
                    mContextAwareHelper.clearAvailableContext();
                    // And clear the ViewModelStore
                    if (!isChangingConfigurations()) {//When there is configuration information, it returns: true (the screen rotates and the background is killed). When there is no configuration information, it returns: false (the application is actively killed).
                        getViewModelStore().clear();
                    }
                }
            	}
        	});

Suddenly realized that the original ViewModel processes the data cleaning operation by observing the Activity life cycle through the life cycle

My official account, "Ann Ann Ann Android", is free to learn knowledge.

Topics: Android