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.
- 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.
class MainActivityViewModel : ViewModel() { | |
private val _counterLiveData = MutableLiveData(0) | |
val counterLiveData: LiveData<Int> = _counterLiveData | |
fun incrementByOne() { | |
_counterLiveData.value = _counterLiveData.value?.plus(1) | |
} | |
} |
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 -:
val viewModel = MainActivityViewModel() | |
findViewById<Button>(R.id.btn_increment).setOnClickListener { | |
viewModel.incrementByOne() | |
} | |
viewModel.counterLiveData.observe(this) { counter -> | |
findViewById<TextView>(R.id.tv_counter).text = "$counter" | |
} |
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 -:
val viewModel = ViewModelProvider(this)[MainActivityViewModel::class.java] | |
findViewById<Button>(R.id.btn_increment).setOnClickListener { | |
viewModel.incrementByOne() | |
} | |
viewModel.counterLiveData.observe(this) { counter -> | |
findViewById<TextView>(R.id.tv_counter).text = "$counter" | |
} |
Job Offers
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.
public constructor( | |
owner: ViewModelStoreOwner | |
) : this(owner.viewModelStore, defaultFactory(owner)) |
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.