ViewModels can hold data that survives configuration changes and helps to keep logic out of your UI.
- Introduction
- Application Architecture
- Project
- ViewModel
- Coroutines
- DiceRoller
- Scramble
- non-JVM platforms
- Pending
Introduction
ViewModel helps ensure data consistency across platforms, while enabling UIs to be customized for each platform’s distinct appearance. You can continue developing your UI with Compose on Android and SwiftUI on iOS.
The ViewModel class exposes state to the UI and encapsulates related business logic. Its principal advantage is that it caches state and persists it through configuration changes. This means that your UI doesn’t have to fetch data again when navigating between activities, or following configuration changes, such as when rotating the screen.
Model–view–viewmodel (MVVM) is an architectural pattern in computer software that facilitates the separation of the development of a graphical user interface (GUI; the view) from the development of the business logic or back-end logic (the model) such that the view is not dependent upon any specific model platform.

The viewmodel of MVVM is a value converter, meaning it is responsible for exposing (converting) the data objects from the model in such a way they can be easily managed and presented. In this respect, the viewmodel is more model than view, and handles most (if not all) of the view’s display logic.
Application Architecture
When building apps, it’s important to separate concerns.
Each app should have at least two layers:
- UI layer: a layer that displays the app data on the screen but is independent of the data.
- Data layer: a layer that stores, retrieves, and exposes the app data.
You can add another layer, called the domain layer, to simplify and reuse the interactions between the UI and data layers.
The arrows in the diagrams in this guide represent dependencies between classes. For example, the domain layer depends on data layer classes.
UI layer
The role of the UI layer, or presentation layer, is to display the application data on the screen.
Whenever the data changes due to a user interaction, such as pressing a button, the UI should update to reflect the changes.
The UI layer is made up of the following components:
- UI elements: components that render the data on the screen. You build these elements using Compose.
- State holders: components that hold the data, expose it to the UI, and handle the app logic. An example state holder is
ViewModel.

Project
To set up the ViewModel in your project:
dependencies: - org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.9.6 - org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.10.2To make ViewModel coroutines work correctly with Desktop, add the kotlinx-coroutines-swing dependency to your project.
ViewModel
In its simplest form, you can create a ViewModel by extending the ViewModel class:
import androidx.lifecycle.ViewModel
class CounterViewModel : ViewModel() {
private val _state = MutableStateFlow(0) val state: StateFlow<Int> = _state.asStateFlow()
fun increment() { _state.value++ }}You might use the viewModel() function to get your ViewModel.
import androidx.lifecycle.viewmodel.compose.viewModel
@Composablefun CounterView( viewmodel: CounterViewModel = viewModel()) { val count by viewmodel.state.collectAsState()
Column { Text("Count: $count") Button(onClick = { viewmodel.increment() }) { Text("Increment") } }}ViewModel survives configuration changes as the configuration changes, and although your composable is reconstructed, the ViewModel remains the same instance — preserving its state.
Most real applications use StateFlow to expose data changes.
In Compose, we use State or MutableState, or flows that can be collected in composable functions.
import androidx.lifecycle.ViewModel
class CounterViewModel : ViewModel() {
private val _state = MutableStateFlow(0) val state: StateFlow<Int> = _state.asStateFlow()
fun increment() { _state.value++ }}collectAsState() is how you collect StateFlow or Flow in Compose.
Coroutines
If you’re using side effects such as LaunchedEffect, DisposableEffect, or any flow collection, you need to ensure they are scoped correctly.
Let’s say we want to fetch a list of items from the network (or any data source) and display it on the screen.
The ViewModel can handle the network request
class ItemsViewModel : ViewModel() {
private val _state = MutableStateFlow<List<String>>(emptyList()) val state: StateFlow<List<String>> = _state
init { fetchData() }
private fun fetchData() { viewModelScope.launch { // Simulate network delay delay(2000) _state.value = listOf("Item1", "Item2", "Item3") } }}And the composable observes the results:
@Composablefun ItemsView(viewModel: ItemsViewModel = viewModel()) {
val items by viewModel.state.collectAsState()
if (items.isEmpty()) { Text("Loading data...") } else { LazyColumn { items(items) { item -> Text(text = item, modifier = Modifier.padding(8.dp)) Divider() } } }}When running coroutines in a ViewModel, remember that the ViewModel.viewModelScope value is tied to the Dispatchers.Main.immediate value.
DiceRoller
The following is an example implementation of a ViewModel for a screen that allows the user to roll dice.
data class DiceState( val firstDieValue: Int? = null, val secondDieValue: Int? = null, val numberOfRolls: Int = 0,)
class DiceRollViewModel : ViewModel() {
// Expose screen UI state private val state = MutableStateFlow(DiceState()) val uiState: StateFlow<DiceState> = state.asStateFlow()
// Handle business logic fun rollDice() { state.update { currentState -> currentState.copy( firstDieValue = Random.nextInt(from = 1, until = 7), secondDieValue = Random.nextInt(from = 1, until = 7), numberOfRolls = currentState.numberOfRolls + 1, ) } }}You can then access the ViewModel from an activity as follows:
import androidx.lifecycle.viewmodel.compose.viewModel
@Composablefun DiceRollView(viewModel: DiceRollViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsState()
Column { Text(uiState.toString())
Button(onClick = { viewModel.rollDice() }) { Text("Roll Dice") } }}Scramble
The Unscramble app is a single player word scrambler game. The app displays a scrambled word, and the player has to guess the word using all the letters shown. The player scores points if the word is correct. Otherwise, the player can try to guess the word any number of times. The app can also skip the current word. In the top right corner, the app displays the word count, which is the number of scrambled words played in the current game. There are 10 scrambled words per game.
Add a model class for state UI called GameState.
Make it a data class and add a variable for the current scrambled word.
non-JVM platforms
On non-JVM platforms, objects cannot be instantiated using type reflection. So in common code you cannot call the viewModel() function without parameters: every time a ViewModel instance is created, you need to provide at least an initializer as an argument.
If only an initializer is provided, the library creates a default factory under the hood. But you can implement your own factories and call more explicit versions of the common viewModel(...) function, just like with Jetpack Compose.
Pending
Read more about benefits of using ViewModel and all the features in the primary documentation for ViewModel.
You can see the implementation in practice in our official sample (Only Android)