Search code examples
androidandroid-architecture-componentsandroid-viewmodelandroid-mvvm

How Android ViewModel insures that, it has to bind with the same Activity or Fragment(in case of Screen Rotation)?


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!


Solution

  • 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