DEV3 Technologies home

DEV3 Developer Forum

How do I validate a Flutter form with TextFormField and show errors only after submit?

Flutter helpSolved0 views
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?

#flutter#forms#validation

0

1 reply

dev3 · Accepted answer

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

  • A validator returns an error string or 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.
  • The default 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.
  • Use _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

  • Creating the GlobalKey inside build resets the form on every rebuild.
  • Always dispose controllers.
  • Strict email regexes reject valid addresses; a light check plus server verification is better.
0

Your reply

Every post is reviewed by DEV3 before it appears. Usually reviewed within 24 hours.

Markdown supported. Use ``` for code blocks. Images are not allowed.