Table of Contents
Why ViewModel Exists
In a simple app, you might store all your UI data and logic directly in an Activity or Fragment. This works only until the first configuration change such as screen rotation. When the screen rotates, the system destroys and recreates the Activity and its Fragments. Any data you stored in their fields is lost, which leads to bugs like resetting a counter or losing a list of items whenever the device rotates.
A ViewModel solves this problem by providing a place to keep UI related data that survives configuration changes. It belongs to the logical layer of your screen, not to the visual layer. The Activity or Fragment may be recreated many times, but the ViewModel stays in memory as long as its associated scope exists.
The key idea is that ViewModel separates screen data and logic from Android UI classes. This makes your code more robust to lifecycle events and also makes it easier to test. Instead of asking an Activity for data, the UI observes the ViewModel, and the ViewModel exposes only what the UI needs.
A ViewModel exists to hold and manage UI related data in a lifecycle conscious way and to survive configuration changes such as screen rotations.
Basic ViewModel Usage
To use ViewModel, you first add the ViewModel dependency in your Gradle module. Once that is available, you create a class that extends ViewModel. This class contains the data and logic that your screen needs. Unlike an Activity, a ViewModel does not reference UI elements like TextView or Button. It holds plain Kotlin properties, business logic, and observable data.
A simple ViewModel might look like this:
import androidx.lifecycle.ViewModel
class CounterViewModel : ViewModel() {
var count: Int = 0
fun increment() {
count++
}
}
The ViewModel class has no Android UI imports. It is just a Kotlin class that extends ViewModel. The increment function updates the internal count. The UI layer should never reach in and change this data on its own. Instead, the UI calls functions like increment, and then reads or observes the updated state.
To use this ViewModel in an Activity, you obtain an instance that is tied to that Activity's lifecycle. You do not manually create it with CounterViewModel(). Instead, you ask a ViewModelProvider or use the by viewModels() or by activityViewModels() property delegates. For example, in an Activity:
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private val viewModel: CounterViewModel by viewModels()
// ...
}
This tells Android to give you a CounterViewModel instance that is associated with this Activity. When the Activity is recreated due to a configuration change, viewModel refers to the same CounterViewModel instance as before. Only when the Activity is finished for good, the system clears its ViewModel instance.
The important point is that you never worry about keeping the ViewModel alive yourself. You just ask for it, and the system manages its lifecycle for you.
ViewModel and Lifecycle
A ViewModel is lifecycle aware in the sense that it knows when its associated UI owner is finished and can release resources at that time. It is not tied to the visual lifecycle callbacks of an Activity or Fragment such as onCreate, onStart, or onResume. Instead, the platform creates the ViewModel the first time you request it, and then reuses it for subsequent recreations of the same owner.
When an Activity or Fragment is destroyed because the user leaves the screen for good, its ViewModel is cleared. At that moment, the system calls onCleared() on the ViewModel. You can override this method to clean up resources such as closing database connections, canceling coroutines, or stopping timers that the ViewModel started.
For example:
class CounterViewModel : ViewModel() {
override fun onCleared() {
super.onCleared()
// Cancel jobs or release resources here
}
}
Although ViewModel is lifecycle aware, it does not observe lifecycle events directly in this basic usage. It relies on the fact that it is connected to a ViewModelStoreOwner such as an Activity or Fragment, and this owner decides when the ViewModel should be created and when it should be discarded. This is different from classes that implement LifecycleObserver or similar approaches.
Because ViewModel outlives configuration changes, you must be careful about what you put inside it. Any reference held by the ViewModel will also live across configuration changes. This is very useful for long-living operations such as loading data or caching results, but it also means that you should avoid holding references that could cause memory leaks.
ViewModel Scope and Ownership
A ViewModel instance is tied to a particular ViewModelStoreOwner. Common owners are an Activity or a Fragment. If you obtain a ViewModel from an Activity, that ViewModel is shared across all fragments that use the activityViewModels() delegate inside that Activity. If you obtain a ViewModel from a Fragment with by viewModels(), it is private to that fragment.
The ownership of the ViewModel decides how long it lives and who can access it. An Activity scoped ViewModel survives fragment changes inside the same Activity. A Fragment scoped ViewModel dies when the fragment is removed. This scoping is crucial when you design your architecture, especially when you want to share data between fragments or keep data while switching fragments.
For example, a SharedViewModel created like this in two fragments:
class FirstFragment : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
}
class SecondFragment : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
}
will be the same instance for both fragments because they use the same Activity as the owner. If you used by viewModels() in each fragment, you would get two separate instances that do not share data.
When you work with navigation components or nested fragments, you can also scope ViewModels to a navigation graph or to a parent fragment by specifying the correct owner when you get the ViewModel. The general rule is that you decide the scope of the ViewModel based on how wide you need to share the data.
State Management in ViewModel
The most important responsibility of a ViewModel is managing UI state. The ViewModel should expose state in a way that the UI can observe and react to. For simple examples, you might use plain properties and manually update the UI in lifecycle callbacks. For more realistic use, you can use observable types, but those will be covered in a separate chapter.
From the ViewModel perspective, it manages the state as plain Kotlin data. For instance, a screen that displays user details might have a UserViewModel that holds the current user name, loading flags, and error messages. The ViewModel decides when to fetch or update the user data and changes its internal state accordingly. The UI simply observes and presents these changes.
A typical ViewModel maintains clear separation between internal mutable state and externally visible state. Inside the ViewModel, you might have private mutable properties, and public read only properties for the UI. This prevents the UI from accidentally modifying the state and encourages a unidirectional data flow where events go from UI to ViewModel, and states go from ViewModel to UI.
For example:
class LoginViewModel : ViewModel() {
var isLoading: Boolean = false
private set
var errorMessage: String? = null
private set
fun login(username: String, password: String) {
isLoading = true
errorMessage = null
// Perform login asynchronously in practice
// When done, update isLoading and errorMessage accordingly
}
}
Here, login is the only way to start a login attempt, and it also controls how the internal state changes. The UI calls login in response to user actions, and then reads the updated state. In a more advanced setup, the state would be exposed as observable data types and the UI would automatically update when the state changes.
Managing state in ViewModel keeps your Activity and Fragment lean. They do not need to know how data is fetched, validated, or cached. They only react to state changes by updating views. This separation is central to modern Android architectures such as MVVM.
What Should and Should Not Be in a ViewModel
When you design a ViewModel, it is important to choose carefully what to place inside it. A ViewModel should contain data that is required to display the UI, along with the logic that transforms or fetches that data. This includes things like counters, lists of items, user input that you want to keep during configuration changes, and references to repository classes or use case classes that actually load the data.
You should not put direct references to Android UI elements in a ViewModel. This means no Context from an Activity, no View, and no TextView or Button. The ViewModel must be independent from the view layer so that it can survive configuration changes and be testable. If you need a Context in a ViewModel, there is a special subclass called AndroidViewModel that provides an application context. That class is used when you truly need access to the application level context, such as when you work with system services or resources that depend on context.
The ViewModel can hold references to other components like repositories or data sources that handle data operations. These are usually provided through dependency injection so that the ViewModel itself stays clean and easy to construct, but the creation and injection details belong to another topic.
Another rule is that long running tasks should not block the main thread inside ViewModel. Instead, you start asynchronous work using threads or coroutines and update the ViewModel state when those tasks complete. This keeps the UI responsive while still concentrating all logic in one place.
Do not store references to Activity, Fragment, or View inside a ViewModel. This can cause memory leaks because the ViewModel outlives these UI components across configuration changes.
ViewModel and Configuration Changes
Configuration changes such as rotation, language change, or system theme change cause the system to recreate the Activity or Fragment. Without ViewModel, you would need to manually save and restore every piece of data using mechanisms like onSaveInstanceState or persistence. This is repetitive and error prone.
With ViewModel, when the Activity is recreated, you simply ask for the same ViewModel again, and the system returns the existing instance. The internal state is preserved automatically as long as your process remains alive. This makes the experience seamless for the user. For example, if a list of items was already loaded and displayed, rotating the device does not trigger another load, because the ViewModel still holds the list.
However, ViewModel does not replace all state saving. If the process is killed in the background to free memory, the ViewModel is also lost. In that case, when the user returns to your app, the ViewModel is created again from scratch. For critical data that must survive process death, you still need other solutions such as persistence or saved state handles. Those features are separate and build on top of ViewModel.
In everyday development, ViewModel is the first line of defense against losing UI state during normal configuration changes. It greatly reduces the amount of manual work you have to do to keep your screens consistent when the device orientation or other configuration changes.
Testing ViewModel Logic
Because ViewModel is just a Kotlin class without references to UI elements, it is straightforward to test its logic. You can create an instance of your ViewModel in a unit test and call its methods directly, then check that its internal state changes as expected. There is no need to run an emulator or start an Activity for this.
For example, a test might create CounterViewModel, call increment twice, and then assert that count equals 2. In a more complex case, you might simulate a login attempt and assert that loading flags and error messages are set correctly. Since the ViewModel does not know about Android views, it can be tested with normal JVM unit tests.
Keeping logic in ViewModel and out of Activity and Fragment is a key way to improve testability. If you place network calls, validation logic, and state management in ViewModel, you can write repeatable unit tests that exercise your screen behavior without relying on the device environment.
In summary, a ViewModel plays a central role in modern Android architecture. It keeps UI data across configuration changes, contains the logic for transforming and fetching data, and creates a clean boundary between visual components and application logic, which results in more robust and testable apps.