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
observeAsState
for LiveData: ConvertLiveData
to Compose state usingobserveAsState
for 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
SavedStateHandle
to 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!