I am trying to use ViewModel in my app. The question comes to my mind is How View Model survives configuration changes. I read number of blog posts saying that "
It will create a HolderFragment to add to your activity or your fragment, it's invisible, when the configuration changed, activity destroyed, but holder fragment is still alive
and that make sense.But I tried to explore more on this and found out that in support library 27.1.0+ they have removed the HolderFragment with Description saying
Deprecate ViewModelStores.of() and the HolderFragment it relies on as they are no longer needed link for android.googlesource.
Now the question is How they are doing the same thing right now?
The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations. Architecture Components provides ViewModel helper class for the UI controller that is responsible for preparing data for the UI.
When using this module, ViewModel objects receive a SavedStateHandle object through its constructor. This object is a key-value map that lets you write and retrieve objects to and from the saved state. These values persist after the process is killed by the system and remain available through the same object.
ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way.It is the main component in the MVVM architecture. ViewModel can be created with activity context or fragment context. When a ViewModel object is created, it is stored inside Activity OR FragmentManager.
The ViewModel exists from when you first request a ViewModel until the activity is finished and destroyed.
ViewModels created with ViewModelProviders.of()
method are stored in ViewModelStore
hashmap, so the real question is how ViewModelStore
is stored.
For activities, this logic is straightforward. ViewModelStore
is stored using onRetainNonConfigurationInstance
method:
@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
Object custom = onRetainCustomNonConfigurationInstance();
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
// No one called getViewModelStore(), so see if there was an existing
// ViewModelStore from our last NonConfigurationInstance
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
For fragments, things are a bit more complicated. FragmentManagerImpl
now has a field called mNonConfig
:
private FragmentManagerViewModel mNonConfig;
which stores a hashmap of a Fragment's UUID and a ViewModelStore
.
This mNonConfig
field is initialized in FragmentManagerImpl#attachController
method:
public void attachController(@NonNull FragmentHostCallback host,
@NonNull FragmentContainer container, @Nullable final Fragment parent) {
if (mHost != null) throw new IllegalStateException("Already attached");
mHost = host;
mContainer = container;
mParent = parent;
if (mParent != null) {
// Since the callback depends on us being the primary navigation fragment,
// update our callback now that we have a parent so that we have the correct
// state by default
updateOnBackPressedCallbackEnabled();
}
// Set up the OnBackPressedCallback
if (host instanceof OnBackPressedDispatcherOwner) {
OnBackPressedDispatcherOwner dispatcherOwner = ((OnBackPressedDispatcherOwner) host);
mOnBackPressedDispatcher = dispatcherOwner.getOnBackPressedDispatcher();
LifecycleOwner owner = parent != null ? parent : dispatcherOwner;
mOnBackPressedDispatcher.addCallback(owner, mOnBackPressedCallback);
}
// Get the FragmentManagerViewModel
if (parent != null) {
mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
} else if (host instanceof ViewModelStoreOwner) {
ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
} else {
mNonConfig = new FragmentManagerViewModel(false);
}
}
Basically, in order to retrieve a ViewModel
in Activity
one should call ViewModelProviders.of(this).get(SomeViewModel.class)
. Now, if we look into of
it looks like below:
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
Application application = checkApplication(activity);
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(activity.getViewModelStore(), factory);
}
So, the important part is this method - activity.getViewModelStore()
because it returns a wrapper object (HashMap
holder) for all of your ViewModel
objects and if it can survive config changes so can all of your ViewModel
objects:
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.");
}
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
mViewModelStore
will be either restored from NonConfigurationInstances
or created from scratch. Pretty much, NonConfigurationInstances
is the object that survives config changes and hence is used to store the ViewModelStore
. That's why the same ViewModelStore
object is returned after rotation - it's stored in the config change independent NonConfigurationInstances
:
If you look into onRetainNonConfigurationInstance()
, you will actually that your ViewModelStore
is saved there:
public final Object onRetainNonConfigurationInstance() {
...
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = viewModelStore;
return nci;
}
Also, it will be cleared only if onDestroy
for a non-config change reason is called:
...
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();
}
}
}
});
...
A similar trick is used for storing a ViewModel
for a Fragment
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With