dev3 ·
I have email and password fields. I want inline error messages, to validate when the user presses Submit, and to move focus to the first invalid field. I currently check the controllers manually. What is the idiomatic way?
0
DEV3 Developer Forum
Every post is reviewed first. Usually reviewed within 24 hours.
I have email and password fields. I want inline error messages, to validate when the user presses Submit, and to move focus to the first invalid field. I currently check the controllers manually. What is the idiomatic way?
Use Form + GlobalKey<FormState>
class LoginForm extends StatefulWidget {
const LoginForm({super.key});
@override
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _email = TextEditingController();
final _password = TextEditingController();
@override
void dispose() {
_email.dispose();
_password.dispose();
super.dispose();
}
void _submit() {
if (!_formKey.currentState!.validate()) return;
// all validators passed
login(_email.text.trim(), _password.text);
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(children: [
TextFormField(
controller: _email,
keyboardType: TextInputType.emailAddress,
autofillHints: const [AutofillHints.email],
textInputAction: TextInputAction.next,
decoration: const InputDecoration(labelText: 'Email'),
validator: (v) {
final s = v?.trim() ?? '';
if (s.isEmpty) return 'Email is required';
if (!s.contains('@')) return 'Enter a valid email';
return null; // null means valid
},
),
TextFormField(
controller: _password,
obscureText: true,
decoration: const InputDecoration(labelText: 'Password'),
validator: (v) =>
(v == null || v.length < 8) ? 'At least 8 characters' : null,
onFieldSubmitted: (_) => _submit(),
),
FilledButton(onPressed: _submit, child: const Text('Sign in')),
]),
);
}
}
Details
null when valid.validate() runs every validator and returns false if any fail. It does not move focus, so call FocusScope.of(context).requestFocus(...) on a FocusNode yourself if you want to jump to the first error.AutovalidateMode.disabled shows errors only after validate() is called, which is what the question asks for. Add autovalidateMode: AutovalidateMode.onUserInteraction to the Form if you also want a field to re-validate as the user edits it._formKey.currentState!.save() with onSaved if you prefer not to use controllers.Server errors
Keep a String? _serverError in state and show it through InputDecoration(errorText: _serverError), then clear it on change.
Pitfalls
build resets the form on every rebuild.