ViewModel magic revealed!!!

ViewModel magic revealed!!!

Photo by AltumCode on Unsplash

As an Android developer, I am sure you have come across the term MVVM in your job or at least in interviews**.

Model — View — ViewModel (MVVM) is the industry recognised software architecture pattern that overcomes all drawbacks of MVP and MVC design patterns. MVVM suggests separating the data presentation logic(Views or UI) from the core business logic part of the application.

ViewModel

We all know that MVVM is very popular, especially for the fact that it can survive configuration changes. Let’s quickly go through the implementation of the same, so we can understand directly from the code, that why is MVVM so popular and what it does under the hood that it can survive configuration changes even after Fragment/Activity is destroyed.

  1. First of all, we will create a ViewModel class, i.e, a sub-class which will extends the ViewModel class. For the sake of simplicity, we will add a counter variable in our ViewModel.

2. Now we will declare an instance of ViewModel in our Fragment. Now many of you, who are new to this concept of ViewModels might be wondering, as ViewModel is just a class, we can simply declare an instance of the ViewModel and access public variables in our Activity/Fragment.
Something like -:

Cool and easy right? NOOO. This will not throw an error, but it will not fill our purpose of retaining counter value on configuration changes. That’s not how ViewModel works. We need something called a ViewModelProvider to create a ViewModel. The correct way to create a ViewModel is -:

Rotate your device and you will notice that the old value of the counter integer is retained and not set to zero unlike the former. But now many of you might be thinking that why on earth can’t we directly create an instance of ViewModel and use it, and what magic is ViewModelProvider doing here?

Let us deep dig into it and connect the dots!!!

ViewModelProvider

1. Activity lifecycle on Configuration changes

Let us discuss first what happens to our activity when ever there is a configuration change such as screen rotation and the most important question why our state, data is lost due to this configuration change??

When a configuration change occurs, the Android destroys the current activity, calling *onPause()*, *onStop()*, and *onDestroy()*. Then the system restarts the activity from the beginning, calling *onCreate()*, *onStart()*, and *onResume()*

I think you got your answer to the question that why our data is not retained when we do any configuration changes? It is because of the Android system destroys the activity and recreates it from scratch, and as the activity is recreated, the old resources and instances of various classes(ViewModelProvider in our case) are first destroyed on calling *onDestroy()*method and later recreated in *onCreate()* .

To summarise, the old instance of the classes inside activity are destroyed when the activity is destroyed and a new instance of the same is created on recreating the activity inside *onCreate()* method.

I know this may sound a bit overwhelming for some, but read this paragraph twice, along with the lifecycle of activity/fragment, and you will get it.

2. ViewModelProvider not destroyed by configuration change??????

So the next question which may have popped up in your mind by now is that how come ViewModelProvider is able to retain the data from ViewModel, because it is also being destroyed and new instance is created of the same in *onCreate()*right? Right, a new instance of ViewModelProvider is created when activity is recreated, but it is the internal implementation of this ViewModelProvider that it is able to return the same instance of ViewModel that was present before the configuration change. Let us check out the implementation of the ViewModelProvider to see how it does it.

Here is the constructor of ViewModelProvider class, which we use to create a simple ViewModel from an activity or fragment, assuming we use the default ViewModelFactory.

Here you can see that we pass something called ViewModelStoreOwner. But in our Activity, we pass *this* while instantiating our ViewModelProvider.

ViewModelProvider(this)[MainActivityViewModel::class.java]

But from where did this ViewModelStoreOwner come from in our Activity? If we check the source code of Activity class, it can be noticed that the Activity class implements the ViewModelStoreOwner interface.

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
ContextAware
,
LifecycleOwner,
ViewModelStoreOwner,
HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner,
OnBackPressedDispatcherOwner,
ActivityResultRegistryOwner,
ActivityResultCaller

We are close!! Now we are clear that ViewModelStoreOwner is doing something which is helping the ViewModelProvider return the same instance of ViewModel which was present before the configuration change.

The responsibility of an implementation of the interface ViewModelStoreOwner is to retain owned ViewModelStore during the configuration changes and call ViewModelStore.clear(), when this scope is going to be destroyed.

public interface ViewModelStoreOwner {
/\*
* Returns owned {*@link ViewModelStore}
\

* **@return a {**@code** ViewModelStore}
*/* @NonNull
ViewModelStore getViewModelStore();
}

Let’s dig further deep into the implementation of the *getViewModelStore()* in our Activity and see what it does.

@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "

            + "Application instance. You can't request ViewModel before onCreate call.");  
}  
ensureViewModelStore();  
*return* mViewModelStore;  

}

If the activity is not created, it returns throwing an error, but if it is it called the *ensureViewModelStore()* method.

void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}

I promise we’re close to the end. Stay tuned if you have reached till here!!!
This method, *ensureViewModelStore()* initially checks for if the viewModelStore is null, then it declares an instance of a class called NonConfigurationInstances, which basically checks for if the instance of mViewModelStore is already present, if it is, then it returns the same instance otherwise a new instance of ViewModelStore() is returned.

You might be wondering now that how come NonConfigurationInstances class is able return the old instance of ViewModelStore, which was present before the configuration change? Shouldn’t this be destroyed as well with the Activity?

if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances mViewModelStore = nc.viewModelStore;
}

The answer is no because NonConfigurationInstances is a static class. We all know that static classes object are not bound to any activity or fragment. It remains in the memory as long as the app is in the memory, or till it is cleared manually or programatically.

static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}

OK! so NonConfigurationInstances, a singleton class, is able to return an old instance of the ViewModelStore, which was present before the configuration change, and that is how our instance and ultimately our data is restored. Makes sense.

But the last question, and I BET IT IS THE LAST is that how this static class knows when to destroy the ViewModelStore instance and when to not??The answer lies in the default constructor of this activity class.

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()) {
getViewModelStore().clear();
}
}
}
});

Inside the *onStateChanged()* method, which is called whenever the state of the activity changes, every time *onDestroy()* is called, it checks if it is because of the configuration change or not??? If not, it simply clears the instance of ViewModelStore from memory and a new instance gets created next time. If the call to ***onDestroy()*** is due to the configuration changes, it does not destroy the instance of ViewModelStore from static class. This is how ViewModelProvider differentiates between *onDestroy()* due to configuration change and *onDestroy()* due to Activity being permanently destroyed.

Yayy!!! Finally we revealed the magical powers of ViewModel and ViewModelProvider. Now we can also create our own implementation of ViewModel. Thanks for reading this long but insightful article. Please share your feedback in comments if you found it helpful or not.

EDIT-: First of all, thanks to everyone for all the love, support and claps on this article. Secondly, as one of our viewers Martin Cazares mentioned in the article comments section, there are few points slightly incorrect in this article, and I feel it is important for me to address the same in this article itself. So thanks to Martin for pointing this out and sorry to the viewers for the confusion.

I will start from the point where things started to go a bit wrong, prior to which is still absolutely right.

@NonNull
@Override
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "

            + "Application instance. You can't request ViewModel before onCreate call.");  
}  
ensureViewModelStore();  
*return* mViewModelStore;  

}

Yes it is true that getViewModelStore() is responsible for returning the retained instance of ViewModelStore using the ensureViewModelStore() method. But let’s revisit the ensureViewModelStore() once again.

void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}

Earlier, I had mentioned that -:

The answer is no because NonConfigurationInstances is a static class. We all know that static classes object are not bound to any activity or fragment. It remains in the memory as long as the app is in the memory, or till it is cleared manually or programatically.

However, this statement is incorrect, and the reason for that is that NonConfigurationInstances is a static class and not a static member. Even though NonConfigurationInstances is static, it is an inner class which is a part of outer ComponentActivity class. As Martin mentioned in the comments

If it were a static member holding the reference of a NonConfigurationInstances it could be the case that it would be linked to the Activity class itself and not to the life cycle of the Activity’s instance.However, it is a static class internally declared in Activity. And as such it definitely gets released from the instance once it is destroyed. In other words. the instance doesn’t remain in the new activity just because the class was declared as static.

In short, static classes!=static members. So clearly, the class being static is not the reason that the old instance of ViewModelStore is being retained. Then what is it? Let’s check out.

As we checked earlier, the old instance of NonConfigurationInstances class is retained by calling the method getLastNonConfigurationInstance(). This function, in turn, returns the mLastNonConfigurationInstances.activity object, and inside the onCreate() method of our Activity, this instance is retained by using the savedBundleInstance object, as mentioned in the comments. This is how the instance of our old ViewModelOwner is retained, not because it is static class!!!

Did you find this article valuable?

Support Hitesh's blog by becoming a sponsor. Any amount is appreciated!