CPS251 Android Development by Scott Shaper

What is a ViewModel?

Introduction

A ViewModel is like a manager for your app's data. It keeps track of information your screen needs, even if the user rotates their phone or leaves and comes back. In Jetpack Compose (and Android in general), ViewModels help you keep your UI and your app's logic separate, making your code easier to understand and maintain.

When to Use a ViewModel

  • When you need to keep data around as the user navigates or rotates the device
  • When you want to separate your UI code from your business logic
  • When you have data that is shared between multiple composables or screens

Main Concepts

  • Lifecycle-aware: ViewModels survive configuration changes (like screen rotation) so your data isn't lost.
  • UI separation: The ViewModel holds the data and logic, while your composables just display it.
  • State holder: The ViewModel is the "single source of truth" for your screen's state.

Dependencies

You will have to add some dependencies to your gradle file for view models to work.

Gradle (build.gradle.kts Module :app)

dependencies {
    // ... your existing dependencies ...
    implementation(libs.androidx.lifecycle.viewmodel.compose)
    // ... rest of your dependencies ...
}

libs.versions.toml

[versions]
# ... your existing versions ...
lifecycle = "2.7.0"  # Add this line

[libraries]
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }

Creating a New Class File

You will need to create a new class file for your ViewModel.

Right click on your package name, it will start with com.

Creating the viewmodel file

Then click New and then Kotlin File/Class

Creating the viewmodel file

In the dialog box select Class and enter the file name "MainViewModel". NOTE: The file name can be anything you want.

Creating the viewmodel file

That will create a class file and place it in the same package as your MainActivity.kt file.

Creating the ViewModel

You have to have a file that is your view model. For this example I will create a file called MainViewModel.kt. It contains the following code:

import androidx.compose.runtime.mutableStateOf
    import androidx.lifecycle.ViewModel
    
    class MainViewModel : ViewModel() {
        private var _count = mutableStateOf(0)
        var count: Int
            get() = _count.value
            private set(value) {
                _count.value = value
            }
    
        fun increment() {
            count = count + 1
        }
    }
    
  

This Kotlin code defines a MainViewModel class, which extends ViewModel. The ViewModel class is part of the Android Architecture Components and is designed to store and manage UI-related data in a lifecycle-conscious way. This means the data within the ViewModel persists across configuration changes (like screen rotations) and the ViewModel itself lives as long as the scope of the UI (e.g., an Activity or Fragment).

Key components:

  • private var _count = mutableStateOf(0): This declares a private mutable state variable named _count and initializes it to 0. mutableStateOf is a function from the Jetpack Compose UI toolkit that creates an observable state holder. When the value of _count changes, any UI elements observing it will automatically recompose (update). By making it private, external classes cannot directly modify this internal state.
  • var count: Int: This declares a public read-only property named count of type Int. It has a custom getter and a private setter:
    • get() = _count.value: The getter simply returns the current value of the private _count.
    • private set(value) { _count.value = value }: The setter is private, meaning that while the count can be read publicly, it can only be modified internally within the MainViewModel class. This enforces a pattern where the UI can observe the state, but cannot directly change it; changes must happen through defined functions in the ViewModel.
  • fun increment(): This is a public function that provides a way to modify the count. When called, it increments the count by 1. This function serves as the controlled entry point for the UI to request a state change.

In summary, this ViewModel manages a simple counter. The UI can observe the count property to display its current value, and it can call the increment() function to request an update to the counter, without directly manipulating the state.

MainActivity.kt

You also must have a mainactivity.kt file. The MainActivity.kt file is the UI that usess the view model for its data. The partial code is shown below:

import androidx.lifecycle.viewmodel.compose.viewModel  //this imports the viewModel file
...

//this is the composable function that uses the view model
@Composable
fun CounterScreen() {
    // Get or create a ViewModel instance
    // viewModel() is a composable function that handles ViewModel lifecycle
    val viewModel: MainViewModel = viewModel()

    // Column arranges its children vertically
    Column(
        modifier = Modifier
            .padding(top = 50.dp)  // Add top padding for better spacing from status bar
            .padding(16.dp)        // Add padding around all sides
    ) {
        // Button that triggers the increment function in ViewModel
        Button(onClick = { viewModel.increment() }) {
            Text("Increment")
        }

        // Text composable that displays the current count
        // It automatically updates when the count changes in ViewModel
        Text("Count: ${viewModel.count}")
    }
}

The key line in the code above is Button(onClick = { viewModel.increment() }) { This is the button that triggers the increment function in the ViewModel.

How this example renders

Above is just a snippet of the code to view the full code, you need to go to my GitHub page and look at the chapter10 mainviewmodel1.kt file and chapter10 mainactivity1.kt file.

How this example renders How this example renders

Tips for Success

  • Use ViewModels for any data you want to keep when the screen changes.
  • Keep your UI code (composables) simple—let the ViewModel handle the logic.
  • Don't put Android Context or UI elements in your ViewModel.

Common Mistakes to Avoid

  • Storing UI elements or Context in the ViewModel (it should only hold data and logic).
  • Trying to use a ViewModel for data that should only exist temporarily (like a text field's current value).
  • Forgetting to use by mutableStateOf for state you want Compose to react to.

Best Practices

  • Use one ViewModel per screen or feature.
  • Expose only the data and functions your UI needs (keep things private when possible).
  • Document what your ViewModel does and what state it holds.