ViewModel is deprecated*
- 1 day ago
- 3 min read

Not sure if it’s just me, but ViewModel is starting to feel increasingly redundant in a Compose-first world.
What if we could skip it entirely?
TL;DR
Housekeeping
The goal of this post is to leverage retain— a relatively new Compose API — and use it to build something that pretends to be a ViewModel, but lives entirely in compose-land.
Let’s lose a few braincells together, shall we.
What’s wrong with ViewModel, exactly?
When ViewModel landed, it solved quite a few problems — configuration changes were a pain, and pretty much no one liked the activity lifecycle.
ViewModel was a godsend, and a lot of weight was put behind it in the following years.
The issue is that it was designed for that world. It does feel a bit weird bringing in a pre-Compose construct to manage state, when Compose already provides pretty much everything out of the box.
Enter retain
Think of it as remember on steroids. It keeps an instance alive even across configuration changes. (full details in the docs)
Sounds familiar?
RetainObserver
We can use RetainObserver to receive information about the state of an object used with retain.
Callbacks such as onEnteredComposition, onExitedComposition, onRetained are a representation of what is happening in the compose layer — and not very relevant in this case.
But there is one specific callback — onRetired — that maps very interestingly. If this callback is called, then it means our composable has permanently left the screen.
We can use it for our own ViewModel-impostor class. Calling it RetainedViewModel sounds good enough, if a bit on the nose.
Easy?
Let’s extend this for our own little CustomRetainedViewModel :
While this works, its usefulness is close to zero.
Without dependency injection, we are stuck explicitly creating an instance of this class ourselves. The horror. 😱
Just make it work
All we really need is a way to for Hilt to provide us with implementations of RetainedViewModel.
Classic caveman approach is using an @EntryPoint — Hilt’s escape hatch for situations where constructor injection is not possible.
It works I guess? It’s also a “meh”. It’s very specific to this certain CustomRetainedViewModel class.
Wait a second!

Setting up the DI
With a regular ViewModel, getting one inside a composable is trivial:
Reminder — viewModel() uses ViewModelProvider.Factory under the hood.
This interface integrates quite well with the existing ViewModelStore infrastructure.
Why not borrow from that approach, but use a creation lambda instead?
Koin provides its own APIs to replicate the same approach as Hilt. That will be covered in part 2 of this post. (lie)
The good
Tied to the composition. Works anywhere you can host a composable. (even on IME services for a sweet system keyboard based in compose 😎)
Scoped to a composable’s lifetime — cleared via onRetired when it permanently leaves composition.
The not so good
A few things to be aware of:
Navigation. Compose Navigation/Navigation3 do not currently work with retain out of the box the way they do with ViewModel. Retained values are forgotten when a composable goes into the back stack.
This will be fixed in future navigation versions via RetainedValueStore
Scoping. Dependencies marked with @ViewModelComponent will need to be moved to other components. (like @ActivityComponent )
The boilerplate. Not exactly pretty. Oh well.
The bad
No SavedStateHandle equivalent. Process death is not handled here.
Be careful with retain. It should not be used with objects that have a shorter lifespan than what retain provides — this can cause memory leaks. The same applies to key inputs passed to retain, which are held onto for as long as the value is retained. 😬
Should someone actually do this?

Personally, I like it. It does feel a bit hacky though for something that should just work™.
ViewModel was also made KMP compatible, which is great — but it still carries a lot of pre-compose baggage, having been retrofitted into KMP after the fact.
My guess is that a first-class, Compose-native solution is around the corner?
Anyways
Hope you found this somewhat useful.
Later.







Comments