ViewModel framework was introduced as part of Android Architecture Components – a set of experimental features for Android that were announced by Google at Google IO 2017 conference.

On the surface, the purpose of ViewModel looks very legitimate – it can be used in order to associate some data with the logical scope of either Activity or Fragment. It means that ViewModel itself and, consequently, the objects referenced by it will be preserved upon configuration change.

The stated advantage of such an approach is that it will reduce the burden placed on Android developers by Activity and Fragment lifecycle.

Sure thing we all need this feature! Like, yesterday!

Comparison of ViewModel to the “old” approach:

But let’s take a step back for a moment.

How did we retain data in e.g. Activity upon configuration change until now? The following code snippet demonstrates the “old” approach:

public class OldApproachActivity extends Activity {

    private static final String USER_ID = "USER_ID";
    private static final String PRODUCT_ID = "PRODUCT_ID";

    private String mUserId;
    private String mProductId;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            mUserId = savedInstanceState.getString(USER_ID);
            mProductId = savedInstanceState.getString(PRODUCT_ID);
        }
        ...
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(USER_ID, mUserId);
        outState.putString(PRODUCT_ID, mProductId);
    }

    ...
}

For comparison, retaining the same variables during configuration change using ViewModel could be done like this:

public class ViewModelActivity extends Activity {

    private MyViewModel mViewModel;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
        ...
    }
    
    ...

    public static class MyViewModel extends ViewModel {
        private String mUserId;
        private String mProductId;
    }

}

If I would need to decide which approach is better based on the simple samples above, it is only natural to choose the one involving ViewModel. It is shorter and clearer.

The no-so-evident gotcha, however, is that the above two code snippets are not functionally equivalent!

During “normal” interaction of the user with the application both approaches produce the same result, but the “old” approach also supports “save and restore” flow, whereas the one involving ViewModel doesn’t.

Save and Restore Flow:

As I explained in the article about Android memory management and save & restore, Android kills applications when the operating system runs low on memory.

In most cases, Android will notify the process that it is about to be killed, thus giving it a chance to save application’s state. Then, when the application is started again (either due to user interaction or automatically), it will be given an opportunity to restore its previous state. This flow is called “save and restore”.

Let’s review one specific example.

Suppose that the application has a back stack of Activities when Android decides to kill it.

Before killing the application, Android will call onSaveInstanceState(Bundle) method of each Activity on the back stack. Developers need to use the provided Bundle in order to persist data that will allow to restore the state of each Activity in the future.

In addition, Android will automatically save the state of the back stack itself (which Activities are shown and their order).

When the application is restarted, the system will automatically restore the previous state of the back stack. User can then navigate back to all Activities that had been present on the back stack when the application was killed, and Android will automatically recreate them.

When Activities from the back stack are being recreated, Android passes the respective saved data to onCreate(Bundle) methods. This saved state data can be used by developers in order to restore the state of each Activity.

The process of save and restore of each Activity can be roughly summarized as follows:

android_save_restore

Correct implementation of “save and restore” flow allows the user to have a fluent experience that is not affected by Android’s memory management activities.

If you go back and read the code demonstrating the “old” approach above, you will notice that in addition to supporting configuration changes, it also supports save and restore flow. In case of the new approach involving ViewModel, however, save and restore flow results in loss of developer managed state.

Note that only developer managed state will be lost. Android will always do its part and restore the state it is responsible for. It is only the state kept in ViewModel that will not be restored.

This state inconsistency is much worse than not restoring any state at all. Imagine that the user can navigate back to previous Activities, but their state was restored only partially (e.g. some references that must not be nullable during normal operation are nulls).

In such a case, you will be lucky if application just crashes with NPE. Then, at least, the user will know that something bad happened and have a chance to start application from scratch.

Much worse if the application does not crash, but the user is stuck with e.g. infinite progress indication or inconsistent UI.

The worst of all scenarios is if the application seems to be working, but silently corrupts user’s data.

The video below demonstrates a bug related to save and restore flow that I found in the new StackOverflow application for Android:

The bug demonstrated in this video is the least dangerous of all save & restore bugs – just user interface inconsistency, followed by application’s crash. Imagine how frustrated it would be if I would write a detailed answer and only then the application would crash and loose all this content.

Why Care About Save and Restore Flow:

Some developers argue that save and restore is such a rare scenario, that it can be ignored until proven otherwise.

Other developers don’t go as far as ignoring save and restore, but still say that if it occurs then it means that the user haven’t interacted with the application for a long time. In this case, they say, we can just assume that previous state is outdated anyway and restart the application from scratch.

You might’ve heard additional forms of this argument. The assumption underlying all these claims is that save and restore does not occur very often.

The reality, however, is that save and restore flow is being constantly experienced by majority of your users.

I experience save and restore on a daily basis on my personal Samsung Galaxy S4 having 2GB of RAM.

Take a look at this chart from AppBrain showing the most used Android phones as of May 2017:

Most used Android phones

Note how your average user is more likely to have Samsung Galaxy S3 than Samsung Galaxy S7 Edge. Also note that the second most used phone is Samsung Galaxy Grand Prime which has just 1GB of RAM!

Given that Samsung phones are on the high performance side, users of other phones are most likely to have similar or even smaller amounts of RAM.

Think about it – a huge amount of your users (probably above 50%, unless you have a niche market) own phones that have less than 2GB of RAM. [Edit: I got this estimation completely right; read the article about Android memory management and save & restore for a much more detailed market overview]

On my Samsung Galaxy S4, which has 2GB of RAM, Android starts killing applications after I have approximately 10 applications open in the background. Your average user will have more than 10 applications in his/her “recents” list and will experience save & restore on a daily basis too.

Therefore, handling of save & restore is not optional. Professional Android developers who care about their users must implement a first class support for save and restore in their applications.

Equipped with this knowledge let’s get back to ViewModel architecture component.

Usefulness of ViewModel:

In the previous section I explained why support for save & restore flow is not optional and must always be implemented. In addition, earlier in this article you saw that implementation of save and restore also handles configuration changes.

So, why would you want to use ViewModel then? I mean, if you must implement support for save & restore anyway, and this functionality also handles configuration changes, why invest additional effort into ViewModel?

Note that this will always be additional effort because even if you use ViewModel, you will still need to implement the logic that handles save & restore.

There is one use case for ViewModel that was stated by many developers. Since it seems to be so resonating with the community, let’s discuss it in details.

Turns out that there is an upper limit on the amount of data that can be stored during save and restore flow. I couldn’t find the exact number, but looks like it is at approximately 500kB.

If you attempt to save more than 500kB of data during onSaveInstanceState() call, you will be presented with TransactionTooLargeException.

In this situation it becomes very tempting to use ViewModel in order to retain big data sets during configuration changes.

In my opinion, under no circumstances should large data sets be “stored” in either Activities or Fragments. Let me explain why using two imaginary scenarios.

Imagine that the user interacts with Activity and fetches 1MB of data from the web. If all this data is stored in Activity then when the user clicks “back” button and navigates to the previous screen this data is lost. If at this point the user decides to open the same Activity again, then all the data will need to be re-fetched from the network. This network call duplication due to a simple back-and-forth between screens is not a very good design.

Now imagine that we implement search functionality. In this case, even if the user searches for the same string, we must still fetch a new data from the network upon each search request. We are not concerned with back-and-forth between screens anymore because we hit the network anyway. It might seem that storing search results in Activity is a valid design choice in this case, but imagine that you discover at some point that you need the search results in other part of the application. The fact that search results are stored in Activity becomes an obstacle because there is no reliable way to access them outside of this Activity.

The above examples show that while ViewModel can be used in order to retain large data sets upon rotation, it is probably a bad design choice to start with. The fact that it can be done doesn’t imply that it should be done.

The only state that should be stored in Activities and Fragments is UI state and data identifiers.

Large data sets should be stored in Application scope, or, depending on the requirements, persisted to SQLite or some other persistent storage.

Since there is no clear valid use case for ViewModel Architecture Component, its introduction can be hardly justified.

ViewModel Architecture Component Considered Harmful:

In the previous section we discussed why ViewModel is somewhat useless addition to developer’s toolbox. However, I think that it is not just useless, but harmful.

As I said earlier, many developers (especially inexperienced ones) don’t realize the importance of proper save & restore handling.

Furthermore, even when developers do implement the logic that handles save & restore, tests for this feature rarely included in the testing plan. [if you’re interested, I wrote an article that explains how to test save & restore properly]

While save and restore support is often neglected and rarely tested (in my experience; YMMV), configuration changes support is much more common. In fact, any application that is not locked to portrait mode will be thoroughly tested in context of screen orientation change.

Until today, the fact that support for configuration changes also provided support for most parts of save and restore flow mitigated the problem a bit. And even when bugs with save and restore were found, they could be fixed while keeping the general design intact.

Introduction of ViewModel changes the situation cardinally. Now developers will be able to implement support for configuration changes while completely ignoring save and restore flow.

It doesn’t take too much imagination to see that the following two step scenario will take place on many projects:

  1. Application is released without save and restore support. Users are being bombarded by save and restore bugs.
  2. Upon realization of the problem, developers “hack” save and restore support alongside ViewModel approach.

The immediate result of lack of save and restore support will be user facing bugs. Some of them might be as innocent as NPE crashes, others might silently corrupt users data.

Assuming best case scenario of application crashes, developers will be able to quickly identify the issues using various “crashlytics” tools. If the application will just hung or silently corrupt user’s data, then developers will need to investigate the problem based on 1-star user reviews in Google Play.

Once the root cause of the bugs will be identified, save and restore support will need to be implemented. However, it is not likely that developers will refactor the applications in order to get rid of ViewModel – at this point it will be too risky and time consuming. Therefore, save and restore support will be just hacked on top of the existing design. The result of these hacks will be long term maintainability issues and more bugs.

And there is more to it.

Since it is so simple to retain data in Activities and Fragments using ViewModel approach, the average amount of memory consumed by applications will increase. Even if different components will need access to the same data, it will be very easy to have each of them to retain its own dataset.

Applications will consume more memory, which will lead to more frequent killing of processes by Android, which will lead to the bugs in save and restore to affect even more users.

Positive feedback of bugs…

In addition, ViewModel has its own lifecycle. Therefore, it will make the overall lifecycle of the application more complex.

The rule that ViewModel should not reference Activities is the manifestation of the aforementioned complication of lifecycle, which developers need to understand and handle.

Summary:

There has already been an attempt by Google to make the handling of configuration changes easier by preserving data – Loaders. Yes, Loaders handled concurrency (in a terrible way), but the main motivation behind LoaderManager framework was to preserve data upon configuration changes.

Loaders consumed a lot of community’s attention and effort until we learned their limitations and costs. I myself spent a considerable time learning about Loaders, implementing them and then refactoring out of my codebase when I realized how much they complicate the design.

I believe that ViewModel will eventually share the fate of Loaders. It is not that horribly complicated, but its existence is completely unjustified IMHO.

In addition, this “architecture component” has nothing to do with MVVM architectural pattern.

ViewModel class can be used in implementation of MVC and MVP. Hell, it can be used in the “classical Android approach” when all the logic resides in a single Activity.

Naming is important in software, and, in my opinion, the fact that this library was so misleadingly named speaks volumes. A library that was intended to be used by millions of developers who build applications for billions of users should have been held to a higher standards.

That’s all I wanted to say about ViewModel.

Please leave your comments and questions below, and consider subscribing to our newsletter if you liked this post.

If you liked this post, then you'll surely like my courses

Subscribe for new posts!