Understanding State in Jetpack Compose with Simple Examples | by Mukesh Rajput | Nov, 2024

Jetpack Compose is a modern UI toolkit for building Android apps, and one of its key concepts is state. In this blog, we’ll break down what state means, why it’s important, and how to use it.

In Jetpack Compose, state represents data that can change over time and directly updates the UI whenever it changes. Think of it as a connection between your app’s data and how it looks on the screen.

For example, if you’re building a counter app, the number displayed on the screen is the state. Every time you press a button to increment or decrement the number, the UI updates automatically.

  • Dynamic UI: State ensures your UI updates whenever the underlying data changes.
  • Simpler Code: No need to manually refresh views — Compose handles it for you.
  • Reactive Programming: Changes in state trigger UI re-composition, making apps more responsive.

1. Using ‘remember’ and ‘mutableStateOf

To define and use state, Compose provides tools like remember and mutableStateOf. Let’s look at a simple counter example.

@Composable
fun CounterApp() {
var count by remember { mutableStateOf(0) }

Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Count: $count", style = MaterialTheme.typography.h4)
Spacer(modifier = Modifier.height(16.dp))
Row {
Button(onClick = { count++ }) {
Text("Increment")
}
Spacer(modifier = Modifier.width(8.dp))
Button(onClick = { count-- }) {
Text("Decrement")
}
}
}
}

  • mutableStateOf(0) creates a piece of state with an initial value of 0.
  • remember ensures the state survives re-compositions.
  • Updating count automatically updates the text on the screen.

2. State Hoisting: Managing State Outside a Composable

State can be passed between composables to make your app more modular. This is known as state hoisting.

Why Hoist State?

  • To make composables reusable and testable.
  • To separate UI from business logic.
@Composable
fun CounterApp() {
var count by remember { mutableStateOf(0) }
CounterScreen(count = count, onIncrement = { count++ }, onDecrement = { count-- })
}

@Composable
fun CounterScreen(count: Int, onIncrement: () -> Unit, onDecrement: () -> Unit) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Count: $count", style = MaterialTheme.typography.h4)
Spacer(modifier = Modifier.height(16.dp))
Row {
Button(onClick = onIncrement) {
Text("Increment")
}
Spacer(modifier = Modifier.width(8.dp))
Button(onClick = onDecrement) {
Text("Decrement")
}
}
}
}

  • CounterApp holds the state.
  • CounterScreen takes the state as input and updates it using callback functions (onIncrement and onDecrement).

3. Handling Complex State with ViewModel

For apps with more complex logic, you can manage state using a ViewModel. Jetpack Compose integrates seamlessly with ViewModel.

Here’s an example using ViewModel for the counter app:

//View Model
class CounterViewModel : ViewModel() {
private val _count = mutableStateOf(0)
val count: State<Int> = _count

fun increment() {
_count.value++
}

fun decrement() {
_count.value--
}
}

//Initialize View Model in Compose Function
@Composable
fun CounterApp(viewModel: CounterViewModel = viewModel()) {
CounterScreen(
count = viewModel.count.value,
onIncrement = { viewModel.increment() },
onDecrement = { viewModel.decrement() }
)
}
//UI Design for Compose App
@Composable
fun CounterScreen(count: Int, onIncrement: () -> Unit, onDecrement: () -> Unit) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Count: $count", style = MaterialTheme.typography.h4)
Spacer(modifier = Modifier.height(16.dp))
Row {
Button(onClick = onIncrement) {
Text("Increment")
}
Spacer(modifier = Modifier.width(8.dp))
Button(onClick = onDecrement) {
Text("Decrement")
}
}
}
}

This separates state management logic into a ViewModel, making your code cleaner and more testable.

  1. Mutating State Directly: Always use provided state management tools (mutableStateOf, ViewModel, etc.) to avoid issues.
  2. Too Many States: Avoid splitting related state into multiple variables; it can make your code harder to maintain.
  3. State in Non-Composables: Only use Compose state tools inside composables.

State in Jetpack Compose is a powerful tool for creating dynamic and responsive UIs. Whether you’re managing a simple counter or a complex app, understanding how state works can save you time and effort.

Leave a Reply