Search code examples
androidmvvmandroid-architecture-componentsandroid-viewmodel

How ViewModel survives configuration change


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?


Solution

  • 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);
            }
        }