Create a button with a callback that shows that it is clicked #Jetpack compose
Video: Create a button with a callback that shows that it is clicked #Jetpack compose by Taught by Celeste AI - AI Coding Coach
Jetpack Compose: Button That Shows It's Been Clicked
remember { mutableStateOf(false) }for the click state. Toggle on click; show a different label or color when clicked. The simplest stateful Composable.
A small upgrade from episode 15: now the button knows it's been clicked, and it visibly changes. We add local state with mutableStateOf and remember.
The Copilot prompt
import androidx.compose.runtime.Composable
// A button that updates its label when clicked
@Composable
fun ClickableButton() {
Copilot generates:
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@Composable
fun ClickableButton() {
val clicked = remember { mutableStateOf(false) }
Button(
onClick = { clicked.value = true },
) {
Text(if (clicked.value) "Clicked!" else "Click me")
}
}
Walkthrough
1. remember { mutableStateOf(false) }. Two pieces:
mutableStateOf(initialValue)creates a state holder — an object whose.valuefield is observable by Compose. Reading.valuefrom a Composable subscribes to changes; writing to.valuetriggers recomposition.remember { ... }caches the lambda's return value across recompositions. Without it, every recomposition would create a new state holder, losing the previous value.
Together: "create the state once, keep it across recompositions, observe it for changes."
2. clicked.value = true in onClick. Mutating the state value triggers recomposition.
3. if (clicked.value) "Clicked!" else "Click me". Reading .value inside the Composable's body subscribes the Composable to that state. When .value changes, Compose recomposes this function.
The cleaner by syntax
The .value boilerplate gets noisy. Kotlin's property delegation lets you skip it:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
@Composable
fun ClickableButton() {
var clicked by remember { mutableStateOf(false) }
Button(onClick = { clicked = true }) {
Text(if (clicked) "Clicked!" else "Click me")
}
}
by remember { ... } reads the underlying MutableState's value via the imports getValue/setValue. Now clicked reads and writes like a regular Boolean variable.
Most modern Compose code uses the by syntax.
Toggle on each click
Button(onClick = { clicked = !clicked }) {
Text(if (clicked) "Clicked!" else "Click me")
}
!clicked flips the boolean. The button alternates between the two labels.
Color change too
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.ui.graphics.Color
@Composable
fun ClickableButton() {
var clicked by remember { mutableStateOf(false) }
Button(
onClick = { clicked = !clicked },
colors = ButtonDefaults.buttonColors(
containerColor = if (clicked) Color.Green else Color.Blue,
),
) {
Text(if (clicked) "Clicked!" else "Click me")
}
}
containerColor picks the background. The button changes color on each click.
For animated transitions, wrap with animateColorAsState:
import androidx.compose.animation.animateColorAsState
val color by animateColorAsState(
targetValue = if (clicked) Color.Green else Color.Blue,
label = "buttonColor",
)
Button(
onClick = { clicked = !clicked },
colors = ButtonDefaults.buttonColors(containerColor = color),
) { ... }
Now the color fades between blue and green over ~300ms.
When to keep state local vs hoist
// Local state — fine for small UI-only flags
@Composable
fun ClickableButton() {
var clicked by remember { mutableStateOf(false) }
// ...
}
// Hoisted — when the parent or ViewModel needs to know
@Composable
fun ClickableButton(clicked: Boolean, onToggle: () -> Unit) {
Button(onClick = onToggle) {
Text(if (clicked) "Clicked!" else "Click me")
}
}
Rule of thumb: state local → keep local. State that affects others → hoist.
A button that briefly shows "Clicked!" feedback is local. A toggle that controls whether other UI is visible should be hoisted.
remember with a key
remember { ... } re-runs the lambda only when its keys change. Without keys, it caches forever. With a key:
@Composable
fun PerItemButton(itemId: Int) {
// Reset state when itemId changes
var clicked by remember(itemId) { mutableStateOf(false) }
// ...
}
When itemId changes (different item passed in), the state resets to false. Without the key, all items would share the same state, breaking when items reorder.
Surviving config changes
Local mutableStateOf is lost on configuration changes (rotation, theme switch, etc.). For state that should survive:
import androidx.compose.runtime.saveable.rememberSaveable
var clicked by rememberSaveable { mutableStateOf(false) }
rememberSaveable persists the state in the Saved Instance State bundle. Survives rotation; lost on app death.
For longer-lived state, use a ViewModel.
Common mistakes
Forgetting remember. Without it, every recomposition creates a new mutableStateOf(false), so the state is always false. Symptom: the button never seems to be clicked.
Forgetting the by imports. var x by remember { mutableStateOf(false) } requires import androidx.compose.runtime.getValue and setValue. Compiler error otherwise.
Mutating state during composition. clicked = true inside the Composable body (not in a callback) is an infinite loop — mutation triggers recomposition triggers mutation. Always mutate in callbacks or LaunchedEffect.
Sharing state via top-level var. Defeats the purpose of remember and breaks across screens. Use mutableStateOf inside remember, or hoist to a ViewModel.
Confusing by and =. var clicked by remember { mutableStateOf(false) } is a delegated property — reads and writes go through the State object. val state = remember { mutableStateOf(false) } is a regular val holding the state object — access via state.value.
What's next
Episode 17: Remove last items from a list and array. A small list-mutation puzzle covering removeAt, dropLast, and the differences between mutable and immutable.
Recap
var clicked by remember { mutableStateOf(false) } for local state. remember caches across recompositions; mutableStateOf is the observable state holder; by lets you read/write as a plain variable. Mutate in callbacks (onClick), not in the Composable body. For state that should survive rotation, use rememberSaveable. For state shared across screens or ViewModels, hoist via parameters.
Next episode: removing last items from a list.