Jetpack Compose: Create a person detail with only a username
Video: Jetpack Compose: Create a person detail with only a username by Taught by Celeste AI - AI Coding Coach
Jetpack Compose: Person Detail Card from a Username
@Composable fun PersonDetail(username: String)with aColumnofTexts. Stateless, parameter-driven, easy to preview. The minimal "show me this object" UI.
A small Compose puzzle: a card that displays a username. The interesting part is what we don't add — no state, no callbacks, no business logic. Pure render.
The Copilot prompt
import androidx.compose.runtime.Composable
// A composable that displays person detail given just a username
@Composable
fun PersonDetail(username: String) {
Copilot generates:
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun PersonDetail(username: String) {
Surface(
color = MaterialTheme.colorScheme.surface,
modifier = Modifier.padding(16.dp),
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Username",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Text(
text = username,
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface,
)
}
}
}
Walkthrough
1. Surface. Material 3's container — sets a background color and elevation. Wraps the actual content. Without it, the content sits directly on whatever's behind it.
2. Column. Stacks children vertically. With Modifier.padding(16.dp), the contents are inset 16dp from the surface edges.
3. Two Texts. A small label ("Username") and the actual value. Two different typography styles — labelMedium for the field name, headlineSmall for the value. Different colors too.
This is the standard "label + value" pattern for any read-only detail field.
Why stateless?
This Composable has no remember, no state, no callbacks. Just a parameter in, layout out. That makes it:
- Trivially testable. Render with any string, snapshot the result.
- Reusable. Drop into a list, a search result, a profile screen — anywhere a username needs to display.
- Predictable. Same input always produces the same output.
For a single user's profile screen, you'd add fields:
@Composable
fun PersonDetail(person: Person) {
Column(modifier = Modifier.padding(16.dp)) {
LabeledField("Username", person.username)
LabeledField("Email", person.email)
LabeledField("Joined", person.joinedDate.toString())
}
}
@Composable
fun LabeledField(label: String, value: String) {
Column(modifier = Modifier.padding(vertical = 8.dp)) {
Text(label, style = MaterialTheme.typography.labelMedium)
Text(value, style = MaterialTheme.typography.bodyLarge)
}
}
The LabeledField Composable is the reusable atom; PersonDetail is the composition.
Adding a Preview
For development, add @Preview so Android Studio shows the rendered Composable:
import androidx.compose.ui.tooling.preview.Preview
@Preview(showBackground = true)
@Composable
fun PersonDetailPreview() {
MaterialTheme {
PersonDetail(username = "alice")
}
}
Now Android Studio renders this preview alongside the source. Edit the layout, see the change instantly — no emulator boot required.
Multiple previews for different states:
@Preview(name = "Short name")
@Composable
fun ShortNamePreview() = MaterialTheme { PersonDetail(username = "al") }
@Preview(name = "Long name")
@Composable
fun LongNamePreview() = MaterialTheme { PersonDetail(username = "alice_in_wonderland_2024") }
@Preview(name = "Empty", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun DarkPreview() = MaterialTheme { PersonDetail(username = "") }
Test edge cases visually before any user sees them.
Adding interactivity (later)
When you add a button or text field, hoist the state up:
@Composable
fun PersonDetail(
username: String,
onEditClick: () -> Unit,
) {
Column {
Text(username)
Button(onClick = onEditClick) { Text("Edit") }
}
}
onEditClick is a hoisted parameter. The Composable still doesn't own state — it just emits an event up.
Modifier conventions
Notice the Composable doesn't take a modifier parameter, but should:
@Composable
fun PersonDetail(
username: String,
modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier.padding(16.dp),
// ...
) { /* ... */ }
}
The convention: every reusable Composable accepts a modifier: Modifier = Modifier parameter. Callers can adjust positioning, sizing, padding without forking the Composable.
PersonDetail(
username = "alice",
modifier = Modifier.fillMaxWidth().padding(8.dp),
)
Common mistakes
Hardcoding the username. Always pass via parameter. State (the data) hoists up.
Reading state inside without remember. A Composable can't have its own mutable state without remember { mutableStateOf(...) }. For purely-derived display, no state needed.
Skipping MaterialTheme. Without a theme wrapper, MaterialTheme.colorScheme is undefined. Always wrap your top-level Composable in MaterialTheme { ... } (in real screens, that's done by the Activity's setContent).
Forgetting modifier parameter. Forced callers to wrap in additional Box(modifier = ...) for layout control. Always accept and forward modifier.
Mixing material (Material 2) and material3. Different colors, different styling. Pick one consistently — Material 3 for new code.
What's next
Episode 22: TextField for name entry. A two-piece Composable: text input plus the live display of what was typed.
Recap
@Composable fun PersonDetail(username: String, modifier: Modifier = Modifier). Stateless, parameter-driven, easy to preview. Wrap in Surface for a background, Column for vertical layout. Use Material 3 typography (labelMedium, headlineSmall) and color tokens (onSurface, onSurfaceVariant). Add @Preview Composables for visual development; cover edge cases (empty, long, dark mode). Always accept and forward a modifier.
Next episode: TextField input.