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
- viewmodel class definition
class AndroidFactoryViewModel(app:Application): AndroidViewModel(app) { fun print(){ logEE("Create using Android default factory viewmodel") } }
- 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
- viewmodel class definition code
class SimpleFactoryViewModel: ViewModel() { fun print(){ logEE("Create with simple factory viewmodel") } }
- 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
- 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
-
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
- 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
- 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) } ...... } }
- viewmodel class definition
class CustomSimpleViewModel(private val data: String) : ViewModel() { fun print() { logEE(data) } }
- 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:
- Join dependency
implementation 'androidx.activity:activity-ktx:1.2.2'
- Create viewmodel class
class EnTrustModel : ViewModel() { fun print(){ logEE("Official account \"Android\" Free learning") } }
- Instantiate viewmodel
private val wtModel: EnTrustModel by viewModels<EnTrustModel> { ViewModelProvider.NewInstanceFactory() }
- 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:
- EventBus
- Interface callback
- 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:
- 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() } }
- 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 }) } } }
- 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 }) } } }
- 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
- viewmodel must not reference views
- 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
- 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,
- 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; }
- 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
- 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
- get
final ViewModel get(String key) { return mMap.get(key); }
It's simple. It's based on key obtain ViewModel
- 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)
- 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.