Jetpack Compose has revolutionized Android UI development by introducing a declarative and reactive way to build user interfaces. However, managing UI-related data remains a critical aspect of app development. This is where ViewModel comes into play. In this blog post, we’ll explore how to use ViewModel in Jetpack Compose, its benefits, and best practices. We’ll also include plenty of code samples to help you integrate ViewModel seamlessly into your Compose-based apps.
What is a ViewModel in Jetpack Compose?
The ViewModel is a class designed to store and manage UI-related data in a lifecycle-conscious way. In Jetpack Compose, ViewModel works hand-in-hand with Compose’s state management to ensure that your UI remains consistent and responsive, even during configuration changes like screen rotations.
Key Benefits of Using ViewModel in Compose
- Lifecycle Awareness: ViewModel survives configuration changes, ensuring data persistence.
- Separation of Concerns: ViewModel separates UI logic from the UI components, making your code cleaner and more maintainable.
- State Management: ViewModel integrates seamlessly with Compose’s state management, enabling reactive UIs.
- Improved Testability: By decoupling business logic from the UI, ViewModel makes your app easier to test.
How Does ViewModel Work in Jetpack Compose?
In Jetpack Compose, ViewModel is used to manage state and business logic outside of composable functions. Composable functions are designed to be stateless, meaning they should not hold any data that needs to persist across recompositions. Instead, the state is hoisted to a ViewModel, which is then observed by the composable functions.
ViewModel Lifecycle in Compose
The lifecycle of a ViewModel in Compose is tied to the lifecycle of the composable that uses it. When the composable is removed from the composition, the ViewModel is cleared from memory. However, during configuration changes, the ViewModel persists, ensuring data consistency.
Implementing ViewModel in Jetpack Compose
Let’s walk through the steps to implement a ViewModel in a Jetpack Compose application. We’ll start with a simple example and gradually build on it.
Step 1: Add Dependencies
To use ViewModel in your Compose project, add the necessary dependencies to your build.gradle file:
dependencies {
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1"
    implementation "androidx.activity:activity-compose:1.7.0"
    implementation "androidx.compose.runtime:runtime-livedata:1.4.0"
}Step 2: Create a ViewModel Class
Create a new class that extends ViewModel. This class will hold the data that you want to preserve during configuration changes.
import androidx.lifecycle.ViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.LiveData
class CounterViewModel : ViewModel() {
    private val _counter = MutableLiveData(0)
    val counter: LiveData<Int> get() = _counter
    fun incrementCounter() {
        _counter.value = (_counter.value ?: 0) + 1
    }
}In this example, we’ve created a CounterViewModel class that contains a LiveData object to store a counter value. The incrementCounter() method is used to update the counter.
Step 3: Use ViewModel in a Composable
To use the ViewModel in your composable function, you need to obtain an instance of it using the viewModel() function.
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CounterScreen()
        }
    }
}
@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
    val counter by viewModel.counter.observeAsState(0)
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "Counter: $counter", modifier = Modifier.padding(16.dp))
        Button(onClick = { viewModel.incrementCounter() }) {
            Text(text = "Increment")
        }
    }
}In this example, we’re using the viewModel() function to obtain an instance of CounterViewModel. The observeAsState() extension function is used to convert LiveData into Compose’s state, allowing the UI to react to changes in the counter value.
ViewModel and State Hoisting in Compose
State hoisting is a key concept in Jetpack Compose, where state is moved to a higher-level composable or ViewModel to make the composable stateless. This approach ensures that the composable is reusable and easier to test.
Example: State Hoisting with ViewModel
Let’s extend our previous example to include a list of items stored in the ViewModel.
class ItemViewModel : ViewModel() {
    private val _itemList = MutableLiveData<List<String>>(listOf("Item 1", "Item 2", "Item 3"))
    val itemList: LiveData<List<String>> get() = _itemList
    fun addItem(item: String) {
        val currentList = _itemList.value?.toMutableList() ?: mutableListOf()
        currentList.add(item)
        _itemList.value = currentList
    }
}
@Composable
fun ItemScreen(viewModel: ItemViewModel = viewModel()) {
    val itemList by viewModel.itemList.observeAsState(emptyList())
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        itemList.forEach { item ->
            Text(text = item, modifier = Modifier.padding(8.dp))
        }
        Button(onClick = { viewModel.addItem("New Item") }) {
            Text(text = "Add Item")
        }
    }
}In this example, the ItemViewModel manages a list of items, and the ItemScreen composable observes the list and updates the UI accordingly.
Best Practices for Using ViewModel in Compose
- Keep Composables Stateless: Hoist state to the ViewModel to make your composables reusable and testable.
- Use observeAsStatefor LiveData: ConvertLiveDatato Compose state usingobserveAsStatefor seamless integration.
- Avoid Business Logic in Composables: Move all business logic to the ViewModel to keep your UI layer clean.
- Leverage SavedStateHandle for Critical Data: Use SavedStateHandleto persist data that must survive process death.
Conclusion
ViewModel is an essential component for managing UI-related data in Jetpack Compose. By separating state and business logic from the UI layer, ViewModel helps you build scalable, maintainable, and testable apps. Combined with Compose’s state management, ViewModel provides a powerful solution for handling data in modern Android development.
By following the examples and best practices outlined in this post, you’ll be well-equipped to integrate ViewModel in Jetpack Compose into your projects. Whether you’re building a simple counter app or a complex list-based UI, ViewModel ensures your app remains robust and responsive.
With these tips and examples, you’re ready to harness the power of ViewModel in Jetpack Compose and take your Android development skills to the next level. Happy coding!