You have used ViewModel many times, but do you truly know what purpose it serves, how it is created and how it survives rotation?
In day-to-day Android work, we usually create ViewModels in different ways:
- Directly with ViewModelProvider:
val vm = ViewModelProvider(this, factory).get(MyViewModel::class.java)
- With Hilt (DI)
val vm: MyViewModel = hiltViewModel()
// Hilt hides the provider way, but internally the idea is still similar
What is the purpose of ViewModel
ViewModel is just a class that:
- holds UI state (example loading, failure, success state),
- survives configuration changes (example screen rotation, theme changes),
- gets cleared only when the screen (like activity or fragment) is actually finished
In this blog, we’ll build a mini version of this system ourselves — so the “magic” becomes clear.
What we want to achieve
Before writing code, let’s define the goals (this keeps the implementation clean):
- ViewModel should survive configuration changes: ViewModel instance should not be cleared when activity is recreated due to configuration changes like screen rotation.
- Same ViewModel class can exist in multiple Activities, but should not be shared: Two different Activities can both use SomeViewModel class, but each Activity should have its own separate instance.
- One Activity can have multiple ViewModels, but only one instance per ViewModel class: Inside a single Activity: We can have HomeViewModel, ProfileViewModel, etc.; But for each ViewModel class, there should be only one instance; And when the Activity is truly finished (not rotation), all its ViewModels should be cleared.
Mental model: how we can achieve this
Think of the whole system as two-level storage
Level 1: Store ViewModels for an Activity
Since a single Activity can have multiple ViewModels, we need a container that can store them all together
So we create a class called ViewModelStore which holds ViewModels in a HashMap:
- key = canonical(fully qualified including path) name of ViewModel class (e.g,: “com.app.ProfileViewModel”)
- value = actual ViewModel instance
That way:
- if Activity asks again for ProfileViewModel, we return the same instance from the map
- if it doesn’t exist, we create and store it
- we can clear it when activity is destroyed by calling clear method on map
This solves: “single Activity → multiple ViewModels” and “one instance per ViewModel class”.
Level 2: Store ViewModelStore outside Activity
Now comes the main trick
If ViewModelStore lives inside Activity, it will die whenever Activity dies.
But Activity dies in two situations:
- Configuration change → should NOT clear ViewModels
- Actual finish (back press) → should clear ViewModels
So we need to store ViewModelStore outside Activity in something that stays alive across rotation
The simplest “lives-through-the-app” place is the Application class.
So inside Application, we keep another HashMap:
- key = canonical name of Activity class (example: “com.app.ProfileActivity”)
- value = the Activity’s ViewModelStore
This solves: “do not lose ViewModels on rotation”.
Let’s dive into some code for creating the core components that will help us create our own ViewModel:
Core Components
MyViewModel
Base contract for ViewModel class.
interface MyViewModel {
// Called before actually destroying the viewmodel instance
fun onCleared() {}
}
MyViewModelStore
Class that stores multiple ViewModels in a HashMap where the key is the ViewModel’s canonical name and the value is the ViewModel instance.
class MyViewModelStore {
private val map = HashMap<String, MyViewModel>()
fun put(key: String, viewModel: MyViewModel) {
val oldViewModel = map.get(key)
oldViewModel?.onCleared()
map[key] = viewModel
}
fun get(key: String): MyViewModel? {
return map[key]
}
fun clear() {
for (vm in map.values) {
vm.onCleared()
}
map.clear()
}
}
MyViewModelStoreOwner
The owner contract. Our Activity will implement this so it can provide the correct MyViewModelStore.
interface MyViewModelStoreOwner {
fun getMyViewModelStore(): MyViewModelStore
}
MyViewModelProvider(+ Factory)
The manager. This is the class that returns the same ViewModel instance if it already exists in the store, or creates a new one using the factory when it doesn’t.
class MyViewModelProvider(
private val owner: MyViewModelStoreOwner,
private val factory: MyViewModelFactory
) {
fun <T : MyViewModel> get(modelClass: KClass<T>): T {
val key = modelClass.qualifiedName!! // canonical name
var viewModel = owner.getMyViewModelStore().get(key)
// we check if viewModel already exists else create new
if (modelClass.isInstance(viewModel)) {
return viewModel as T
} else {
viewModel = factory.create(modelClass)
owner.getMyViewModelStore().put(key, viewModel)
}
return viewModel as T
}
interface MyViewModelFactory {
fun <T : MyViewModel> create(modelClass: KClass<T>): T
}
}
Let’s dive into the some practical example:
Counter Example
To make this system easy to understand, we’ll use a simple UI: a counter text and a button. The counter value is our “UI state”, and the whole purpose of this demo is to prove:
- Tap the button → count increases
- Rotate the screen → count should stay the same
- Press back / close the Activity → ViewModel should clear.
CounterApplication
Application class that will be holding the ViewModelStore in HashMap where key will be the canonical name of our ViewModelStoreOwner (like activity).
class CounterApplication: Application() {
private val viewModelStores = HashMap<String, MyViewModelStore>()
fun getViewModelStore(key: String): MyViewModelStore {
return viewModelStores.getOrPut(key) { MyViewModelStore() }
}
fun clearViewModelStore(key: String) {
viewModelStores.get(key)?.clear()
viewModelStores.remove(key)
}
}
CounterViewModel
It holds the count state.
class CounterViewModel : MyViewModel {
private var count = 0
init {
Log.i("CounterViewModel", "ViewModel created")
}
fun getCount(): Int {
return count
}
fun incrementCount() {
count++
}
override fun onCleared() {
super.onCleared()
Log.i("CounterViewModel", "ViewModel cleared")
}
}
CounterViewModelFactory
This will create the ViewModel.
class CounterViewModelFactory: MyViewModelProvider.MyViewModelFactory {
override fun <T : MyViewModel> create(modelClass: KClass<T>): T {
return CounterViewModel() as T
}
}
CounterActivity
Implements MyViewModelStoreOwner that provides ViewModelStore.
Requests the ViewModel through MyViewModelProvider, uses it to render the UI, and clears everything only when the Activity is actually finished (not on configuration changes).
class CounterActivity : AppCompatActivity(), MyViewModelStoreOwner {
private lateinit var counterTv: TextView
private lateinit var counterIncreaseBtn: Button
private val viewModel: CounterViewModel by lazy {
MyViewModelProvider(this, CounterViewModelFactory()).get(CounterViewModel::class)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_counter)
counterTv = findViewById(R.id.textView)
counterIncreaseBtn = findViewById(R.id.button)
counterTv.text = viewModel.getCount().toString()
counterIncreaseBtn.setOnClickListener {
viewModel.incrementCount()
counterTv.text = viewModel.getCount().toString()
}
}
override fun onDestroy() {
super.onDestroy()
// check if activity is destroying due to configuration changes
if (!isChangingConfigurations) {
(application as CounterApplication).clearViewModelStore(this.javaClass.canonicalName!!)
}
}
override fun getMyViewModelStore(): MyViewModelStore {
return (application as CounterApplication).getViewModelStore(this@CounterActivity.javaClass.canonicalName!!)
}
}
Putting it all together (the full picture)
Now our brain can imagine the flow like this:
Activity = ViewModelStoreOwner (as it owns ViewModelStore)
- It doesn’t create ViewModels directly
- It only knows how to provide its ViewModelStore
Application = the long-living storage
- It keeps one ViewModelStore per Activity class
- Rotation happens → Activity recreates → Application still has the store → ViewModels survive
ViewModelStore (as it store ViewModels) = the per-Activity container
- It keeps one ViewModel instance per ViewModel class using a map
ViewModelProvider = the manager
- The provider is the class that “glues everything”
- It checks if ViewModel exists in the store; if yes → returns it; if no → creates it using a Factory, stores it, and returns it
Factory = the creator
- Only job: create ViewModels when Provider needs a new one.
Application holds Activity’s ViewModelStore, and ViewModelStore holds Activity’s ViewModels.
Two hashmaps → two levels
And this is how the “magic” works :)
Source Code: GitHub
Contact Me: LinkedIn | Twitter
Happy coding! ✌️
Top comments (0)