Series of articles look here
Android Jetpack Architecture Components — What is Jetpack?
Android Jetpack architecture component — Lifecycle usage
Android Jetpack Architecture component — Lifecycle Principles
Android Jetpack Architecture Components – This article takes you through the ViewModel and how it works
Android Jetpack architecture component – LiveData Usage
Android Jetpack Architecture component – LiveData Principles
An overview of the
Earlier we talked about the use and rationale of Lifecycle. Today we’re going to talk about the viewModel. Originally, I was going to write the usage and the principle separately, but I looked at the principle of the ViewModel, and it was very simple, so I decided to put the two together. So let’s get down to business.
What is a ViewModel?
The ViewModel is designed to store and manage data related to the interface in a life-cycle oriented manner. We know that the Activity destroys and rebuilds when the screen rotates, and that it allows the data to survive configuration changes such as the screen rotation.
Ah? So one has to ask, why don’t we just save the data with onSaveInstanceState() and then read the data at onCreate()? This approach is really only suitable for small amounts of data, and it requires serialization. After all, the Bundle transfers data in a limited size.
In addition, when activities and fragments interact with data, our cost is relatively high. The ViewModel solves this problem for us.
So it’s easier and more efficient to separate the View’s presentation of data ownership from the UI controller logic.
ViewModel lifecycle
Let’s start with a picture from the official website:
The figure above illustrates the various life cycle states in which an Activity goes through a screen rotation and then ends. The corresponding ViewModel life cycle is displayed next to the Activity life cycle. This diagram only shows the activity-related life cycle, which is the same on the Fragment.
Normally, we get a ViewModel in the Activity’s onCreate(), but the onCreate() method can be called multiple times, such as screen rotation, so the ViewModel’s lifetime is actually the first time the instance is retrieved until the current page is completely destroyed.
The use of the ViewModel
So now we’re going to write a demo in ViewModel. The ViewModel is created for the first time until the page is completely destroyed. So let’s take a scene where the screen rotates.
Used in an Activity
Now that we need to verify that the ViewModel can actually store data while the screen rotates, let’s take a timer as an example. Let’s not use the ViewModel and see what happens.
/**
* @date:2021/2/22
* @author:Silence
* @describe:
**/
class TimeCounter : LifecycleObserver {
private var canCount = false
private var count = 0
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
private fun start() {
canCount = true
Thread {
while (canCount) {
Log.i(TAG, "start: $count")
Thread.sleep(1000)
count++
}
}.start()
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private fun stop() {
canCount = false
Log.i(TAG, "stop: ")
}
companion object {
private const val TAG = "TimeCounter"
}
}
Copy the code
As you can see, we start the timer on the OnResume page and Stop it on the Stop page. Then we bind the observer to the activity, and we run it to verify the effect:
As you can see, when we rotate the screen, the timer data is reinitialized because the page life cycle is reexecuted. So how do we solve this problem with the ViewModel? So let’s first write a ViewModel. The code is as follows:
class MyViewModel(var count: Int = 0) : ViewModel()
Copy the code
Then we define the current ViewModel in the Activity as follows:
private val viewModel by lazy { ViewModelProvider(this).get(MyViewModel::class.java) }
Copy the code
We then add an input parameter to TimeCounter, passing in the current ViewModel when adding an observer. The code is as follows:
lifecycle.addObserver(TimeCounter(viewModel))
Copy the code
Change count to viewModel.count. So now let’s look at the effect:
Oh, yes. The next thing we need to do is we may need different input parameters in different scenarios. So how do you pass parameters inside the ViewModel? Don’t worry, the ViewModel gives us a Factory. We can solve this problem with the factory class. Directly on the code:
class MyViewModelFactory(var count: Int) : ViewModelProvider.Factory { override fun <T : ViewModel? > create(modelClass: Class<T>): T { return MyViewModel(count) as T } }Copy the code
Now that we’ve defined this factory class, how do we use it? Not to panic, the ViewModelProvider also provides Factory parameters, just change the definition to the following code:
private val viewModel by lazy { ViewModelProvider(this, MyViewModelFactory(10)).get(MyViewModel::class.java) }
Copy the code
So that solves the initial value problem. Let’s take a look at the effect:
Well, it’s settled. But wait, can’t I just create a new ViewModel and pass in the initial values? Why bother with a factory class? However, if a value is passed through the new ViewModel method, it is bound to the Activity’s life cycle, so remember not to define the initial value using the new pass method.
Use in fragments
In daily development, the communication between activities and fragments is a very common problem, which needs to be handled by defining relevant interfaces. In addition, both fragments must deal with situations where the other Fragment has not yet been created or is not visible. We solve this problem by sharing the Activity’s ViewModel. Now, let’s go straight to the code. We just need to define the ViewModel in the Fragment like this:
private val viewModel by lazy { activity? .let { ViewModelProvider(this).get(MyViewModel::class.java) } }Copy the code
Note that we need to pass in the context of the Activity, not the Fragment. This approach has the following advantages:
- The Activity does not need to perform any action, nor does it need to know anything about this communication.
- Fragments don’t need to know each other except for ViewModel conventions. If one Fragment disappears, the other Fragment continues to work as usual.
- Each Fragment has its own life cycle and is not affected by the life cycle of the other Fragment. If one Fragment replaces another, the interface will continue to work without any problems.
ViwModel implementation principle
Since we need to see how the ViewModel works, let’s go back to the ViewModel lifecycle diagram and see that the onCleared method of the ViewModel is called after the Activity is completely destroyed. The onCleared method of the ViewModel is called when the onCleared method is called.
public abstract class ViewModel {
@Nullable
private final Map<String, Object> mBagOfTags = new HashMap<>();
private volatile boolean mCleared = false;
@SuppressWarnings("WeakerAccess")
protected void onCleared() {
}
@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
closeWithRuntimeException(value);
}
}
}
onCleared();
}
...
}
Copy the code
You can see that the onCleared method is called in the Clear method of the ViewModel, so let’s see where the clear method is called.
public class ViewModelStore { ... public final void clear() { for (ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); }}Copy the code
ViewModelStore clear (); ViewModelStore Clear (); ViewModelStore Clear ();
getLifecycle().addObserver(new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (event == Lifecycle.Event.ON_DESTROY) { if (! isChangingConfigurations()) { getViewModelStore().clear(); }}}});Copy the code
You can see that the ViewModelStore clear method is called when the activity calls OnDestory and isChangingConfigurations does not hold. So we can see why there’s an instance of the ViewModel that just calls onDestory. So let’s see what isChangingConfigurations is used for.
If isChangingConfigurations() returns false in onStop(), the Activity is suspended and the resource is no longer needed. You can release the referenced resource. If isChangingConfigurations() returns true, the Activity is being destroyed and a new one is created, in which case the referenced resource needs to be used immediately (in the newly created Activity) so that the resource is not released. When a new Activity is created, the resource is available immediately.
We use backtracking to verify that the destruction of the ViewModel does not take place until the Activity is completely finished. What about the creation of the ViewModel? Without further ado, let’s look at the get method:
@NonNull @MainThread 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); } @SuppressWarnings("unchecked") @NonNull @MainThread public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) { ViewModel viewModel = mViewModelStore.get(key); if (modelClass.isInstance(viewModel)) { if (mFactory instanceof OnRequeryFactory) { ((OnRequeryFactory) mFactory).onRequery(viewModel); } return (T) viewModel; } else { //noinspection StatementWithEmptyBody if (viewModel ! = null) { // TODO: log a warning. } } if (mFactory instanceof KeyedFactory) { viewModel = ((KeyedFactory) mFactory).create(key, modelClass); } else { viewModel = mFactory.create(modelClass); } mViewModelStore.put(key, viewModel); return (T) viewModel; }Copy the code
You can see that it first gets the ViewModel instance from the ViewModelStore. If it gets it, it returns it. If you don’t get one, just create one from the factory class and put it in the mViewModelStore. So we know that even though the Activity is recreated, the data source previously stored in the ViewModel remains because the ViewModel is not destroyed. This explains why the ViewModel can solve the problem of storing the page data after the screen is rotated.
conclusion
This article mainly introduces the use and principle of ViewModel. In summary, ViewModel is created when the Activity onCreate for the first time, and stored in the ViewModelStore. Even if the onCreate method is called several times, It’s always reading the ViewModel instance that was last saved in the ViewModelStore. After the Activity is completely destroyed, the onCleared method of the ViewModel is called to clear it.
reference
website
This article, which started on my blog: Android Jetpack Architecture Components, takes you through the ViewModel and how it works
For more articles, please pay attention to my official number:Code the farmers work