Jetpack Compose Buttons and State: Build a Counter App
Make your screen react to taps. Learn Button, onClick, remember, mutableStateOf, rememberSaveable and TextField by building a counter app and a greeter.
A screen that doesn't react to taps is just a picture. In this part, you'll learn how to add a button in Jetpack Compose, handle clicks, and use state so the screen updates by itself. We'll build a tap counter app and a simple name greeter.
Missed the last part? Read Part 5: Build Your First Screen with Jetpack Compose.
Your First Button
Button(onClick = { println("Button clicked!") }) {
Text("Click me")
}
onClick = { ... }– the code inside runs every time someone taps the button.{ Text("Click me") }– what is written on the button.
Simple. But printing to the console isn't very exciting. We want the screen to change.
What Is State? (The Most Important Idea in Compose)
State is any value that can change over time and should appear on the screen. For example:
- A counter number
- Text the user typed
- Whether a checkbox is ticked
Here is the golden rule of Compose:
When state changes, Compose redraws the parts of the screen that use it. Automatically.
You don't tell the screen to update. You just change the value, and Compose does the rest. Magic! ✨
Building a Counter App
Add this composable to MainActivity.kt:
@Composable
fun CounterScreen(modifier: Modifier = Modifier) {
var count by remember { mutableStateOf(0) }
Column(
modifier = modifier
.fillMaxSize()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "You tapped $count times", fontSize = 24.sp)
Spacer(modifier = Modifier.height(16.dp))
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
Button(onClick = { count++ }) {
Text("Add +1")
}
OutlinedButton(onClick = { count = 0 }) {
Text("Reset")
}
}
}
}
Show it inside setContent with CounterScreen(modifier = Modifier.padding(innerPadding)), then press Run. Tap the button, and watch the number go up! 🎉
If
byshows a red error, pressAlt + Enterand importandroidx.compose.runtime.getValueandandroidx.compose.runtime.setValue.
Let's Break Down That One Magic Line
var count by remember { mutableStateOf(0) }
Read it from the inside out:
mutableStateOf(0)– create a value that starts at0, and tell Compose to watch it.remember { ... }– remember this value, so it isn't reset to 0 every time the screen redraws.var count by– let us use it like a normal variable:count++,count = 0.
What happens if you forget each part?
- Without
mutableStateOf: the number changes, but the screen never updates. - Without
remember: the number keeps resetting to 0.
Bonus: Surviving Screen Rotation
Try rotating your phone. The counter goes back to 0! That's because rotating recreates the screen.
Fix it by changing one word:
var count by rememberSaveable { mutableStateOf(0) }
rememberSaveable keeps the value even when the phone rotates.
Reading What the User Types (TextField)
Let's make a greeter. The user types their name, and the app says hello:
@Composable
fun GreeterScreen(modifier: Modifier = Modifier) {
var name by rememberSaveable { mutableStateOf("") }
Column(modifier = modifier.padding(24.dp)) {
OutlinedTextField(
value = name,
onValueChange = { newText -> name = newText },
label = { Text("Your name") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
if (name.isNotBlank()) {
Text("Hello, $name! 👋", fontSize = 22.sp)
}
}
}
How it works:
value = name– the text box shows whatever is inname.onValueChange– every time the user types, we save the new text intoname.- Because
nameis state, the "Hello" message updates as you type. - The
ifonly shows the greeting when the box isn't empty.
Try It Yourself
- Add a "−1" button that decreases the count (hint:
count--). - Don't let the count go below 0 (hint: use
if). - Change the text color to red when the count is more than 10.
Frequently Asked Questions
What is recomposition? It's the word for "Compose redrawing the screen" after state changes. Compose is smart and only redraws the parts that actually changed.
What's the difference between remember and rememberSaveable?
remember keeps the value while the screen is shown. rememberSaveable also keeps it after rotation or when Android temporarily closes your app in the background.
Where should state live in a big app? In bigger apps, we move state into a ViewModel. I explain the best practices in Jetpack Compose: State Management Best Practices.
Quick Recap
Button(onClick = { ... })runs code on tap.- State is data that can change and appears on screen.
remember { mutableStateOf(...) }creates state that Compose watches.- Change the state, and the screen updates by itself.
rememberSaveablesurvives screen rotation.
Next, let's show many items at once. In Part 7: Show a List with LazyColumn, we'll build a scrolling list, just like a real shopping app.