How do ViewModels work behind? (Part 1)

If you ever wondered how ViewModels actually work behind the scenes, you are in the correct place!

Something that I learned over the years as an Android developer is that there is no magic behind any library, but most of the times they implement something you could also implement, and understanding how things work deep inside gives you a good insight to use it correctly.

For this reason, we will be analysing how ViewModels manage to keep their state during configuration changes, since it became a component present almost in every Android project.

If you want to follow along with me and jump directly into the source code, open a terminal, go to the folder in which you would like to clone the project and paste this line ;):

git clone https://android.googlesource.com/platform/frameworks/support 

Where do we start?

Understanding how ViewModels work we need first to see which elements conform the lifecycle-viewmodel library and how they fit together. Once we understand that, we will be ready to look outside and see how the different components are implemented in different places in order to keep the state (these places being classes like Activity, Fragment, etc).

As a first step, we will have a look at the ViewModel class definition. If you cloned the project in your machine, you’ll find the source code in lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle.

The ViewModel class

For simplicity, we will just show small pieces in the code snippets that we consider relevant:

public abstract class ViewModel {
    @Nullable
    private final Map<String, Object> mBagOfTags = new HashMap<>();
    @Nullable
    private final Set<Closeable> mCloseables = new LinkedHashSet<>();
    private volatile boolean mCleared = false;

    ...

}

Notice that ViewModel is an abstract class, but doesn’t contain any abstract method. This is probably a way to stop anyone from creating an instance of the ViewModel class alone, since you cannot create instances from abstract classes.

Apart from that, there is logic written to handle operations that can be closed (that could be IO operations like reading from a database or making a network call) when the ViewModel instance is cleared, but none of that is related to the state retention.

ViewModelStore and ViewModelStoreOwner

Next to the ViewModel class we can find see other classes that I would like to bring your attention to: ViewModelStore and ViewModelStoreOwner.

These two classes are important if we want to understand how ViewModels are retained during configuration changes since the ViewModelStore is the place where ViewModels are hosted, and ViewModelStoreOwner is the representation of a component that owns or contains a ViewModelStore.

Let’s jump into the ViewModelStoreOwner and have a look:

/**
 * A scope that owns [ViewModelStore].
 *
 * A responsibility of an implementation of this interface is to retain owned ViewModelStore
 * during the configuration changes and call [ViewModelStore.clear], when this scope is
 * going to be destroyed.
 *
 * @see ViewTreeViewModelStoreOwner
 */
interface ViewModelStoreOwner {

    /**
     * The owned [ViewModelStore]
     */
    val viewModelStore: ViewModelStore
}

As mentioned, ViewModelStoreOwner is just an interface that represents an entity that owns a ViewModelStore (as the name already indicates) and, as we can read in the code documentation above, the responsibility of the entity (or scope) that implements that interface is to retain the ViewModelStore instance.

Now, let’s have a quick look at the ViewModelStore class (I removed the methods for simplicity):

/**
 * Class to store `ViewModel`s.
 *
 * An instance of `ViewModelStore` must be retained through configuration changes:
 * if an owner of this `ViewModelStore` is destroyed and recreated due to configuration
 * changes, new instance of an owner should still have the same old instance of
 * `ViewModelStore`.
 *
 * If an owner of this `ViewModelStore` is destroyed and is not going to be recreated,
 * then it should call [clear] on this `ViewModelStore`, so `ViewModel`s would
 * be notified that they are no longer used.
 *
 * Use [ViewModelStoreOwner.getViewModelStore] to retrieve a `ViewModelStore` for
 * activities and fragments.
 */
open class ViewModelStore {

    private val map = mutableMapOf<String, ViewModel>()
    ...
}

As the name of the class already indicates, ViewModelStore is the entity responsible of storing the ViewModels that belong to the ViewModelStoreOwner. In other words, each ViewModelStoreOwner contain a ViewModelStore that holds all the ViewModels that belong to it.

The code documentation specifies once again that this instance must be retained through configuration changes. In other words, the instance retention of the ViewModel depends on the specific implementation of the ViewModelStoreOwner. This is Fragment, Activity, and others.

As there are a few implementations to be analysed and that would make this article really long, I decided to split it in two parts, so we will cover only the specific implementation on Activities in this one.

Surviving configuration changes on Activity scope

In the Android framework there are many subtypes of Activity and not all of them know how to handle ViewModels. In fact, only one of the subtypes is actually implementing the ViewModelStoreOwner interface: ComponentActivity.

Before jumping into this class, let me clarify that there are two ComponentActivity classes coming from different packages:

  • androidx.core.app.ComponentActivity
  • androidx.activity.ComponentActivity

Here we are referring to the second one, which is the one actually implementing ViewModelStoreOwner and handling the ViewModel instance retention. This means that if you want to create ViewModels at the Activity scope, you need to use the ComponentActivity as the base for your UI layer or a subtype.

Now that this is clear, let’s have a look at how the ViewModelStoreOwner interface is implemented:

    ....
    // Lazily recreated from NonConfigurationInstances by getViewModelStore()
    private ViewModelStore mViewModelStore;

    ....   

    /**
     * Returns the {@link ViewModelStore} associated with this activity
     * <p>
     * Overriding this method is no longer supported and this method will be made
     * <code>final</code> 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()
     */
    @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;
    }
    ...

Before commenting the code above, let’s clarify a point before it creates any type of confusion. In the code snippet in which we saw the definition of ViewModelStoreOwner, we saw that it only contained val viewModelStore: ViewModelStore and now you see the implementation in ComponentActivity as @NonNull @Override public ViewModelStore getViewModelStore().

This is because ViewModelStoreOwner is written in Kotlin, and ComponentActivity is written in java. Having val in your interface translates to a getter in java. Clarified this point, let’s proceed with the code snippet.

As we can see, there is a call to a method named ensureViewModelStore() and right after it returns the reference to the ViewModelStore declared at the top of the class. Also, pay attention to comment above the variable mViewModelStore Lazily recreated from NonConfigurationInstances by getViewModelStore().

So, what’s this NonConfigurationInstances that recreate our ViewModelStore? And, what is the ensureViewModelStore()? Let’s have a look:

@SuppressWarnings("WeakerAccess") /* synthetic access */
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();
        }
    }
}

Here we see that the method tries to retrieve first the ViewModelStore instance from an object called NonConfigurationInstances that is returned by getLastNonConfigurationInstace().

At this point, we have to understand and explain this concept and go back to the origin of it, which is the android.app.Activity.java class.

The same way we could persist a limited amount of data in the old days by using onSaveInstanceState(Bundle), Activities have a mechanism to persist any kind of data by implementing a method named onRetainNonConfigurationInstance() which in defined as follows:

    /** 
     * Called by the system, as part of destroying an
     * activity due to a configuration change, when it is known that a new
     * instance will immediately be created for the new configuration.  You
     * can return any object you like here, including the activity instance
     * itself, which can later be retrieved by calling
     * {@link #getLastNonConfigurationInstance()} in the new activity
     * instance.
     * ...
     */
public Object onRetainNonConfigurationInstance() {
    return null;
}

This method returns null in the Activity class definition, meaning that it’s not retaining anything. Notice that I left a paragraph from the comment above on purpose, so we can read clearly how this method is meant to be used.

Basically, when the Activity is about to be destroyed, the system will call this method and will persist the Object that is returned, which in this case it would be null. This means that any subtype of this class can override this method and return any type of Object to be persisted by the system, to be later retrieved by calling getLastNonConfigurationInstance() in the new instance, such as the case of ComponentActivity.

At the top of the ComponentActivity class we can see the creation of a nested class called NonConfigurationInstances:

static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
}

And we finally found the place where the ViewModelStore persisted. The NonConfigurationInstances class will hold a reference to the ViewModelStore and will be passed to the system through the onRetainNonConfigurationInstance(). Here we have the specific implementation:

    /**
     * Retain all appropriate non-config state.  You can NOT
     * override this yourself!  Use a {@link androidx.lifecycle.ViewModel} if you want to
     * retain your own non config state.
     */
    @Override
    @Nullable
    @SuppressWarnings("deprecation")
    public final Object onRetainNonConfigurationInstance() {
        // Maintain backward compatibility.
        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;
    }

Finally, we can see how ComponentActivity is forwarding the ViewModelStore by passing it from the last retained instance to a new one, that will be retained next.

Conclusion

In this article we tried to cover how ViewModels work and which other entities are present in the library to make it work. Also, we explained how the retention of its state happens in the ComponentActivity class and the mechanisms behind.

In a later article, I will write a second part focusing on how the ViewModel‘s state is retained in Fragments and, now that their use is slowly disappearing, in Composable functions (from Jetpack Compose).

Stay tuned! 🙂

Leave a Reply

Your email address will not be published.