Part of Github Copilot with Kotlin

Jetpack Compose: Create a person detail with only a username

Sandy LaneSandy Lane

Video: Jetpack Compose: Create a person detail with only a username by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Jetpack Compose: Person Detail Card from a Username

@Composable fun PersonDetail(username: String) with a Column of Texts. 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.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.