Jetpack Compose with Kotlin: Create a composable function with a textfield for name entry
Video: Jetpack Compose with Kotlin: Create a composable function with a textfield for name entry by Taught by Celeste AI - AI Coding Coach
Jetpack Compose: TextField for Name Entry
var name by remember { mutableStateOf("") }.TextField(value = name, onValueChange = { name = it }). The textfield + live preview is the canonical "user input" Compose pattern.
A small puzzle: a text input that updates a label as the user types. Demos Compose's two-way binding pattern via value + onValueChange.
The Copilot prompt
import androidx.compose.runtime.Composable
// Composable with a TextField for name entry, showing the typed value below
@Composable
fun NameEntryScreen() {
Copilot generates:
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Surface
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun NameEntryScreen() {
var name by remember { mutableStateOf("") }
Surface(modifier = Modifier.padding(16.dp)) {
Column {
TextField(
value = name,
onValueChange = { name = it },
label = { Text("Enter your name") },
)
Text(
text = "Hello, $name!",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(top = 16.dp),
)
}
}
}
Walkthrough
1. var name by remember { mutableStateOf("") }. Local state. Each character typed updates name; Compose recomposes any reader.
2. TextField(value, onValueChange, label). The state-driven input.
- value = name — the current text the field displays.
- onValueChange = { name = it } — what to do with new input. Lambda receives the new String; we update our state.
- label = { Text("Enter your name") } — floating label, animates above the field when focused or non-empty.
3. Text("Hello, $name!"). Reads name. Updates on every keystroke.
That's it. Two-way binding without two-way binding — Compose's unidirectional data flow plus the value + onValueChange pattern.
TextField variants
Material 3 has three flavors:
TextField— filled background, line under the input. The default Material style.OutlinedTextField— bordered, no fill. More common in modern apps.BasicTextField— no decoration at all. Use when you want full control over styling.
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") },
placeholder = { Text("e.g., Alice") },
singleLine = true,
)
placeholder shows greyed text when empty. singleLine = true prevents Enter from inserting a newline (common for "name" type fields).
Validation
var name by remember { mutableStateOf("") }
val isValid = name.isNotBlank() && name.length >= 2
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") },
isError = !isValid,
supportingText = {
if (!isValid) Text("Name must be at least 2 characters")
},
)
isError = true makes the field red. supportingText slot shows beneath the field. Combined, the user gets immediate feedback.
Keyboard options
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") },
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Words,
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Next,
),
keyboardActions = KeyboardActions(
onNext = { focusManager.moveFocus(FocusDirection.Down) },
),
)
KeyboardOptions shapes the on-screen keyboard. KeyboardActions handles the IME action (Next, Done, Search, etc.).
For a phone field: KeyboardType.Phone. For email: KeyboardType.Email. Wraps appropriately on Android.
Password fields
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
var password by remember { mutableStateOf("") }
var passwordVisible by remember { mutableStateOf(false) }
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(
if (passwordVisible) Icons.Default.VisibilityOff else Icons.Default.Visibility,
contentDescription = if (passwordVisible) "Hide password" else "Show password",
)
}
},
)
PasswordVisualTransformation() masks the input as dots. Toggle with VisualTransformation.None to reveal.
Hoisting state
For a real form, hoist state to a ViewModel:
class FormViewModel : ViewModel() {
private val _name = MutableStateFlow("")
val name: StateFlow<String> = _name.asStateFlow()
fun onNameChange(value: String) {
_name.value = value
}
}
@Composable
fun NameEntryScreen(vm: FormViewModel = viewModel()) {
val name by vm.name.collectAsState()
TextField(
value = name,
onValueChange = vm::onNameChange,
label = { Text("Name") },
)
}
Now form state survives config changes and is testable without UI.
Common mistakes
Forgetting var in var name by remember. Using val makes the binding read-only — onValueChange = { name = it } errors.
Forgetting getValue/setValue imports. by delegation needs both. Compiler error otherwise.
Updating in a side-effect. onValueChange = { name = it; sendToServer() } fires on every keystroke. Debounce or move heavy work elsewhere (LaunchedEffect, button click).
Wrong KeyboardType. A phone-number field with KeyboardType.Text lets users enter letters. Use Phone.
Single-source confusion. If you keep both var name and var formState, they can drift out of sync. Pick one source of truth.
What's next
Episode 23: A counter button — increments on each click. Add count state + display + increment.
Recap
var text by remember { mutableStateOf("") } for local form state. TextField(value, onValueChange, label) is the standard pattern. OutlinedTextField for borders, BasicTextField for fully custom styling. Validate via isError = !isValid + supportingText. Tune the keyboard with KeyboardOptions. For real forms, hoist to a ViewModel with MutableStateFlow. Mask passwords with PasswordVisualTransformation().
Next episode: counter button.