ViewModel is one of the most popular building blocks for Android applications, but I don’t use it in my projects. Many Android developers, especially the ones who started their career in the “ViewModel era”, might think that I’m crazy. Well, I’m not crazy (though, that’s something a crazy person would probably say). I’m just pragmatic and understand Android architecture well enough to see the full scope of ViewModel’s benefits and drawbacks.
In this post, I’ll explain why I don’t need ViewModel and show you the alternatives that I use.
Separation of Concerns
Back in the dark ages of Android development (b.v.m – before ViewModel), we would write all our code right inside Activities. That of course, led to projects littered with huge God Classes having many thousands of lines of code inside them. Maintaining Android applications had been hellish job and many developers committed suicide. Okay, okay, that last part about suicides wasn’t true. In fact, the first part about God Classes wasn’t entirely correct either, but let’s get to this point step-by-step.
One of the main reasons to use ViewModel is to separate so-called UI logic, which is responsible for “drawing on the screen”, from other application’s concerns:
In my opinion, this kind of separation is a best practice that you should always use in your projects.
That said, MVVM with ViewModel is far from being the only Model-View-x (MVx) architectural pattern for Android applications. The earliest discussion of MVx in Android that I’m aware of dates back to 2010, and, by 2014, MVP was kind of “standard” pattern in Android ecosystem. MVC, MVP, MVI, etc. also enable Separation of Concerns, without the additional complexity associated with ViewModel. So, ViewModel is just one solution out of many.
When it comes to separation of UI logic, I personally use what I call MVC architectural pattern. It’s better than ViewModel-based MVVM because it incorporates the most important architectural insight in Android world – that Activities and Fragments shouldn’t contain UI logic at all.
Preserving State on Configuration Changes
When something about device’s configuration changes (e.g. user rotates the device), the system will destroy and then re-create all Activities and Fragments in your Android application. This means that all the objects referenced from those components exclusively will be “lost”. For example: computed state, data fetched from the network, user input, etc. Obviously, starting from scratch every time a config change occurs isn’t a very good idea, right? Enter ViewModel!
ViewModel has its own lifecycle, which is “longer” than the lifecycles of individual Activities and Fragments. This enables ViewModel instances to survive configuration changes. Then, when the associated Activity or Fragment are re-created by the system, they can reattach to the preserved ViewModel instance and use its state.
Schematically, you can visualize the difference between ViewModel and any other “controller” implementation in this manner:
So, it’s ViewModel’s longer lifecycle that allows it to retain data across configuration changes. However, ViewModel is not the only way to do that.
First and foremost, many Android Views can retain their state automatically. If you create your custom Views, you can achieve the same behavior by overriding
onRestoreInstanceState methods. In fact, you’ll probably want to rely on this mechanism even if you do use ViewModel (e.g. it can be messy to store transitory user input inside ViewModel). So, no benefit for ViewModel in the context of UI state.
Then there is the state your app fetches from the server. That’s the most common reason developers state when asked why they use ViewModel: “we don’t want to re-execute network request(s) on each config change”. This might sound like a no-brainer, but I’m going to challenge this idea.
My very first question would be “how often do your users experience config changes?”. The answer in most cases is “rarely ever”: outside of video players, galleries, games and some other special types of apps, Android users don’t rotate their devices much. And other types of config changes, like window size changes in split-screen mode, are even rarer. So, given most users of most apps don’t experience many config changes, it’s hard to justify any kind of performance optimization to accommodate this edge case. In other words: you can go ahead and re-fetch all the required data from the server, in most cases.
Sure, there are some exceptions, like video streaming apps mentioned earlier. But, even in these special cases, ViewModel might not be the best tool to preserve the state on configuration changes.
So, in most cases, when it comes to preserving network data over config changes, ViewModel is a preliminary optimization. And there is another, truly weird argument in favor of this preliminary optimization: “I want to keep network request alive if config change happens right when the request is executing”. This is like starting with a rare use case that probably doesn’t need optimization to begin with, and then narrowing it down to something even less likely. My default answer for these situations is: just re-fetch the damn data!
Said all that, there are few valid use cases when you’d legitimately want to preserve data across config changes. But, turns out, ViewModel is not the only solution for this task either. After all, we handled this use case long before Google introduced ViewModel component. From keeping the data globally (in Application scope), to using a Service, to retaining data through
onSaveInstanceState, you’ve got many other options at your disposal. Not to mention that you could always implement a “retained controller”, which is exactly what ViewModel is, using retained headless Fragments or
onRetainCustomNonConfigurationInstance method (Google deprecated both approaches to leave no competition for ViewModel, but they still work).
Lastly, the most aggressive strategy for optimization of configuration changes renders ViewModel completely obsolete. I’m referring to the manual handling of config changes using
android:configChanges option inside
AndroidManifest.xml. Most developers either don’t know about this option, or think that it’s intended to avoid dealing with config changes at all. But what this option actually does, is allowing you to tell Android not to kill your Activities (and, subsequently, Fragments) when configuration changes occur. This spares the need for the system to re-create all these heavyweight components. Sure, this means that developers will need to handle config changes manually, so this path requires more experience and effort, but that’s always the case with real performance optimizations. Therefore, if you really need to optimize config changes, that’s what you should use. And when you use this option, there is zero benefit in using ViewModel because everything is retained anyway.
All in all, the bottom line is that you probably don’t need to optimize your config changes, but, even if you do, you probably don’t need ViewModel.
Auto-Clearing when Leaving the Logical Screen
Another common benefit attributed to ViewModel is that its
onCleared method is called when the logical scope of the enclosing Activity or Fragment is finished. As far as I know, there is no alternative to this callback among Android APIs, so it’s a unique feature.
However, in practice, I’ve never needed this callback. Therefore, while I’m sure there are some “clever” use cases for it, it’s just yet another “niche” API that should be reserved for special circumstances, rather than being touted as a “best practice”.
Auto-Managed Coroutine Scope
Lastly, many developers seem to find additional benefits in
viewModelScope property, which is an extension for ViewModel class. The argument goes along the lines of “since this
CoroutineScope is cleared in
onCleared callback, it must be superior to other approaches”.
In practice, there is no single “best” time to cancel a
CoroutineScope. In fact, in many cases, you wouldn’t want to cancel it at all. For example, if your application involves drawings on a
Canvas and you’d want to save them when the user navigates back from the drawing screen, using
viewModelScope can cause serious bugs and lead to user data loss. Oops.
In my opinion, non-cancellable
GlobalScope would be much better default
CoroutineScope to use inside ViewModels. So, the “clever” management of
ViewModel is actually a recipe for tricky bugs.
My criticism of
ViewModel isn’t new. I was among the first to point out the problems with this API when it had been released and correctly predicted its unfortunate fate. Since then, I had worked on many apps in various business domains (finance, medicine, social, productivity) and I didn’t need
ViewModel even once.
In this article, I summarized the mindsets and the technical alternatives that you can use instead of
ViewModel in your Android projects. In my opinion, avoiding this component is a major benefit for long-term maintainability because
ViewModel brings too much complexity into your codebase. There had already been another official API that dealt with optimization of config changes:
Loaders. For several years
Loaders were the “best practice”, until we realized how bad they were and learned to avoid them. Today, new Android developers might not even know what
Loaders are. Sooner or later,
ViewModel will share
It’s not a coincidence that, on the question of
ViewModel's utility, my opinion is echoed by Jake Wharton’s sentiment. We don’t agree on many topics, so the fact that we both think that
ViewModel is bad is yet another reason to stay away of this component in your Android projects.