ViewModel is one of the most popular building blocks in Android applications, but I don’t use it in my projects. To some Android developers, especially to ones who started their career in the “ViewModel era”, this might sound crazy. Well, I’m not crazy, I promise (though, that’s something a crazy person would probably say). I’m just pragmatic and understand Android architecture well enough to know what’s better for me.
So, in this post, I’ll explain why you don’t really need ViewModel and show you the alternatives that I use. To organize our discussion here, in the following sections I’ll discuss the main features of ViewModel framework one-by-one. Let’s go.
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 hell and many developers committed suicide, so something had to be done about it. 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 give devil its due.
So, 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 one of the few best practices that you should always use in your projects. However, you don’t need ViewModel framework for that. Any other MVx pattern (MVC, MVP, etc.) will achieve the same objective, without the additional ViewModel-specific complexity. The earliest discussion of MVC in Android that I’m aware of dates back to 2010, and, by 2014, when I joined, MVP was kind of “standard” among experienced developers. So, in terms of separation of concerns, ViewModel is just one of many available solutions.
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
Now let’s discuss the most important ViewModel’s feature: ViewModel component survives configuration changes.
To remind you, 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. This means that all the objects referenced from those Activities and Fragments 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. Due to its longer lifecycle, ViewModel will stick around for as long as any instance of the same “logical” Activity or Fragment is “in scope”. Therefore, when the respective component is re-created, ViewModel will automatically reattach to the new instance. It’s only when the last instance of the same logical Activity or Fragment is destroyed and won’t be re-created in the future (e.g. user navigated back from a screen) that the respective ViewModel will be cleared.
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 the data during configuration changes. However, way too many developers believe that if you don’t use ViewModel, you’ll necessarily be starting from a clean state on each config change. That’s incorrect.
First and foremost, pretty much all standard Android Views can retain their state automatically. The only precondition to that is to assign unique IDs to these views in XML. If you create your custom Views, you can achieve the same behavior by implementing
onRestoreInstanceState methods. In fact, you’ll probably want to rely on this mechanism even if you do use ViewModel (e.g. it would be extremely dirty to store input fields state in 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 give 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 absolute majority of cases, is “pretty much never”. This is a rare experience for most users (myself included), so it’s not important enough to justify any kind of performance optimization. In other words: you can go ahead and re-fetch all the required data from the server.
Sure, there are some exceptions, like video streaming and navigation apps. However, in most of these exceptional cases, you wouldn’t store the state inside ViewModels anyway because you wouldn’t want to lose it if the user navigates back from a screen.
So, in absolute majority of cases, when it comes to preserving network data over config changes, ViewModel is a preliminary optimization. And, like all preliminary optimizations, it can get worse. See, the most puzzling argument in favor of ViewModel is “I want to keep network request alive if config change happens right when it’s executing”. It leaves me speechless because it’s like starting with a rare use case that probably doesn’t need optimization to begin with, and then narrowing it down to something which is much rarer still. My answer is: just re-fetch the damn data!
Said all that, there are few valid use cases when you’d legitimately want to optimize your code. In addition, some application might have a computed state that needs to be preserved as well. Surely, now you’d need ViewModel, right? Nope. 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, let’s discuss real performance optimizations of config changes. The truth is that the most aggressive optimization strategy in this context renders ViewModel completely obsolete. I’m referring to the manual handling of config changes using
android:configChanges option in
activity tag 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. After all, if you want top performance, sparing the need to re-create all these heavyweight components sounds like the only reasonable thing to do. Sure, this path requires more work and experience, but that’s always the case with real performance optimizations. So, if you really in need of performance, that’s what you should use. And when you use this option, there is zero benefit in using ViewModel as opposed to any other MVx implementation (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/Fragment disappears. 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”.
Well, let’s acknowledge the ugly truth about Kotlin Coroutines: there is no single “best” time to cancel your Coroutine Scopes. In fact, the need to take care of cancellation is, by itself, the biggest problem of Coroutines. For example, if your application involves drawings on Canvas and you’d want to save them when the user leaves the respective screen, using
viewModelScope can cause serious bugs and lead to user data loss. Many developers don’t understand that. Ooops.
In my opinion, non-cancellable
GlobalScope would be much better default Coroutine Scope to use inside ViewModels. So, the “clever” management of
viewModelScope is actually a foot-gun (time-bomb), not a benefit.
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 quite a bit of 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 to avoid ViewModel in your projects. In my opinion, avoiding this component is a major benefit for long-term maintainability because ViewModel brings too much complexity into projects and have too many associated pitfalls. There was another official API that dealt with optimization of config changes – Loaders. We all know how this ended (new Android developers might not even know what Loaders are), and, sooner or later, ViewModel will share Loaders’ fate.
It’s not a coincidence that, on the question of ViewModel’s utility, my opinion is echoed by Jake Wharton’s sentiment. This doesn’t happen very often, so it’s another sign of how amazingly useless and harmful ViewModels are.