I am working on Android ViewModel right now. One Question keep arising in my mind. How ViewModel insures to rebind with the same Activity or Fragment, after screen rotation, despite of we are creating a new object in onCreate or onCreateView?
Is anyone have proper answer for this, please let me know. I had tried to find out solution at so many tutorials.
Thanks in advance!
When a new instance of ViewModelProvider
is created, the first parameter is activity.getViewModelStore()
and according to documentation,
Returns the {@link ViewModelStore} associated with this activity
Overriding this method is no longer supported and this method will be made
final
in a future version of ComponentActivity. @return a {@code ViewModelStore} @throws IllegalStateException if called before the Activity is attached to the Application instance i.e., before onCreate()
it returns a ViewModelStore
object. So what is a ViewModelStore
?
/**
* Class to store {@code ViewModels}.
* <p>
* An instance of {@code ViewModelStore} must be retained through configuration changes:
* if an owner of this {@code ViewModelStore} is destroyed and recreated due to configuration
* changes, new instance of an owner should still have the same old instance of
* {@code ViewModelStore}.
* <p>
* If an owner of this {@code ViewModelStore} is destroyed and is not going to be recreated,
* then it should call {@link #clear()} on this {@code ViewModelStore}, so {@code ViewModels} would
* be notified that they are no longer used.
* <p>
* Use {@link ViewModelStoreOwner#getViewModelStore()} to retrieve a {@code ViewModelStore} for
* activities and fragments.
*/
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
Set<String> keys() {
return new HashSet<>(mMap.keySet());
}
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
}
Basically, it’s a class which has a HashMap
whose key is DEFAULT_KEY + “:” + canonicalName
where DEFAULT_KEY
is androidx.lifecycle.ViewModelProvider.DefaultKey
and value is the ViewModel
That means every activity and fragment has a ViewModelStore
which keeps all the declared ViewModels
in the activity or fragment respectively.
But How ViewModelStore
survives orientation change?
In the documentation of ViewModelStore
it’s defined that
if an owner of this {@code ViewModelStore} is destroyed and recreated due to configuration * changes, new instance of an owner should still have the same old instance of * {@code ViewModelStore}.
So it’s activities responsibility to retain the ViewModelStore
during orientation change.
If we go back to activity.getViewModelStore()
implementation, then we’ll find the answer.
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
The activity does so by using something called NonConfigurationInstances
. NonConfigurationInstances
is a static final class
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
So it first checks whether there is already an viewmodelstore
stored in NonConfigurationInstances
if not it creates a new ViewModelStore
or returns the existing ViewModelStore
.
Then why doesn’t ViewModel
survive low memory or finish()
scenarios?
ViewModelStore
has a clear()
method :
/**
* Clears internal storage and notifies ViewModels that they are no longer used.
*/
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}
So this clear()
method gets called in onDestroy()
of activity or fragment which clears the HashMap
except during configuration changes. Here is the code :
if (mViewModelStore != null && !isChangingConfigurations) {
mViewModelStore.clear();
}
Now you know the secret recipe of how ViewModel survives configuration change.
Happy coding…
IMPORTANT : I don not believe in taking credits of someone else's work. To make it easily available on stack overflow I have copy-pasted everything from article The curious case of Android ViewModel