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 onSaveInstanceState
and 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 viewModelScope
inside ViewModel
is actually a recipe for tricky bugs.
Dependency Injection with Dagger and Hilt Course
Learn Dependency Injection in Android and master Dagger and Hilt dependency injection frameworks.
Go to CourseSummary
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 Loader
s 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 Loader
s are. Sooner or later, ViewModel
will share Loader
s’ fate.
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.
Can you provide a link to Jake Wharton’s opinion on ViewModel?
I’m unable to find it by Googling “Jake Wharton ViewModel”.
I found that Reddit thread where he expressed his opinion that I had in mind, and also a more recent one.
There is another case for viewmodels. Architecture suggested by Google based on single activity+many fragments , each for screen. To change one screen by another they use .replace method, which replaces view of fragment (not fragment itself). So, when you go back to this replaced fragment, is called onActivityCreated where is doing subscription again to observables from viewmodels . So, these observables already contains data fetched from network earlier and this data not refetch from net, just from viewmodel. It’s like local data caching for a given view(fragment in our case).
Is this good design and architecture pattern at all?! I don’t know but think that it’s not so good. In my opinion destroying view of fragment when you navigate to forward screen is BAD idea! This fucking destroying brings so much problems and was the main reason to create viewmodels at all.
It’s better just to .add new fragment above previous and hide previous! So, previous is live, but hidden, when we go back we extremely fast will get a previous screen and we no need viewmodel to show data again. If we need to update data(refetch) we can react on .onShown callback for example.
It’s all good until you need to fetch something from resources or shared preferences in a view model. And suddenly these operations need ubiquitous “context” which view models do not have.
I agree that AAC VM design is terrible. It is instantiated with no-arg constructor by default, standard `by viewModels()` delegate is super verbose if you want a factory, SavedStateHandle is late and shitty.
Speaking of surviving config changes, I know tons of scenarios when you need it, like avoiding parsing back-end data again on screen rotation, or not re-establishing a WS/gRPC connection (which is to be terminated within onCleared). I like the approach with bound service, it has a lot of advantages like serving several UIs or easy elevation to foreground service if you’re uploading something without a visible activity, but config changes are causing a reconnection and the service doesn’t know should it live a bit longer or die right ahead. I’ve tried Loaders for holding a Presenter but they are turned out to be buggy.
All in all, I need a nonConfigurationInstance with a death receipt, and ViewModel with onCleared gives it to me.
P. S. Yes, I’ve authored “Hype-driven Android-development: how an engineering profession turns into marketing” containing AAC VM criticism among others, but damn, this shit is quite handy.
There another example for configuration changes: dark/light mode. I think it’s the most used feature for this days. Additionally viemmodel is better fit for MVI then MVVM. Keep all UI state in viewmodel, always listen it on fragments/composables. I think viewmodel will be with us for a long time
Hello. I don’t think that dark/light config change is as common as you imply. Furthermore, as far as I know, it’s triggered by the user going into settings, so your app won’t even be visible to the user when they decide to change the mode. In my opinion, no reason to spend even one extra minute to optimize this configuration change. That’s surely not a good justification for ViewModel.
Thanks for the answer. dark/light config change can be changed from the notification panel, so the config change happens between onPause() – onStop() lifecycles. Where I live the weather is partly cloudy most of the time so I use it a lot actually. In my company, we struggle with many bugs related to config changes. Lastly, switching dark/light mode from the notification center can be an easy way to change the config, rather than turning the phone. A hint for developers 🙂
Thanks for the great tip! Sometimes, when I work on apps locked to portrait/landscape, I still make the debug build “rotatable” to quickly check config change scenario. Changing the theme is indeed much simpler.
Thanks for the article, I agree there is a lot of unnecessary overhead in Android API. It is good to go back in history to see why all of this happened. The killing of Activity on configuration change was a design decision related to fact that UI used to be defined in XML and it was needed to inflate a new layout for new configuration. You correctly point to the android:configChanges in the manifest as the way how to avoid this and get rid of all of this madness for good. If you write your layout code to be adaptable, you don’t need any of that, you just let re-layout your views for new dimension, that’s all.
There is another layer of complexity that Android team tried to throw at the problem, Fragments. If you have dynamic views based on ViewGroup, you don’t need Fragments at all. There is tons of API complexity that can go away, and it is ironic that with advent of Jetpack Compose, nobody is using XML layouts anymore, yet the monstrosities as Fragments and MVVC is still there.