Let's think about it for a second
TextField is the composable that accepts text input from the user — it's typically paired with `value`/`onValueChange` parameters together with State (from the previous lesson), since Compose's TextField holds no internal state of its own — it has to be controlled by the parent (a controlled component pattern, similar to React). Button is the composable that triggers an `onClick` lambda.
Let's connect this to a real-world scenario
If you write `var email by remember { mutableStateOf("") }` and then `TextField(value = email, onValueChange = { email = it })`, the `email` state updates instantly as the user types, and the TextField shows exactly what the user typed. Clicking the 'Submit' Button lets you validate/submit the `email` value.
Let's look at an example together
@Composable
fun LoginForm() {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
Column(modifier = Modifier.padding(16.dp)) {
TextField(
value = email,
onValueChange = { email = it },
label = { Text("Email") }
)
TextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation()
)
Button(onClick = { println("Login: $email") }) {
Text("Login")
}
}
}Typing into the two TextFields and clicking the Login button should print 'Login: <email>' in Logcat.Try it in 5 minutes
Run the `LoginForm` composable — type in an email/password, click the Login button, and check the Logcat output.
A quick word of caution
Don't rely on client-side form validation alone (email format, password length) — a production app should always include server-side validation too, since client-side validation can be bypassed.