Flutter Forms: Get the list of fields in error Flutter Forms: Get the list of fields in error flutter flutter

Flutter Forms: Get the list of fields in error


I don't think it is possible to get this kind of information from a Form object or a FormState.

But here is a way around to obtain the result you want (focus on the field in error) :

class _MyWidgetState extends State<MyWidget> {  FocusNode _fieldToFocus;  List<FocusNode> _focusNodes;  final _formKey = GlobalKey<FormState>();  final _numberOfFields = 3;  String _emptyFieldValidator(String val, FocusNode focusNode) {    if (val.isEmpty) {      _fieldToFocus ??= focusNode;      return 'This field cannot be empty';    }    return null;  }  @override  void initState() {    super.initState();    _focusNodes =        List<FocusNode>.generate(_numberOfFields, (index) => FocusNode());  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(actions: [        IconButton(          icon: Icon(Icons.check),          onPressed: () {            if (_formKey.currentState.validate()) {              print('Valid form');            } else {              _fieldToFocus?.requestFocus();              _fieldToFocus = null;            }          },        ),      ]),      body: Form(        key: _formKey,        child: Column(children: [          ...List<TextFormField>.generate(            _numberOfFields,            (index) => TextFormField(              decoration: InputDecoration(hintText: "Field $index"),              focusNode: _focusNodes[index],              validator: (val) => _emptyFieldValidator(val, _focusNodes[index]),            ),          ),        ]),      ),    );  }}

You simply need to create a FocusNode for each one of your fields, thanks to that you will be abla to call requestFocus on a precise field (in your case a field considered as invalid). Then in the validator property of your form field, as it is the method called by the FormState.validate(), you need to set a temporary variable which will contains the right FocusNode. In my example I only set the variable _fieldToFocus if it was not already assigned using the ??= operator. After requesting the focus on the node I set _fieldToFocus back to null so it will still works for another validation.

You can try the full test code I have used on DartPad.

Sorry if I have derived a bit from your question but I still hope this will help you.


Expanding on Guillaume's answer, I've wrapped the functionality into a reusable class.

You can view a working example on DartPad here: https://www.dartpad.dev/61c4ccddbf29a343c971ee75e60d1038

import 'package:flutter/material.dart';class FormValidationManager {  final _fieldStates = Map<String, FormFieldValidationState>();  FocusNode getFocusNodeForField(key) {      _ensureExists(key);      return _fieldStates[key].focusNode;  }  FormFieldValidator<T> wrapValidator<T>(String key, FormFieldValidator<T> validator) {      _ensureExists(key);      return (input) {          final result = validator(input);          _fieldStates[key].hasError = (result?.isNotEmpty ?? false);          return result;      };  }  List<FormFieldValidationState> get erroredFields =>      _fieldStates.entries.where((s) => s.value.hasError).map((s) => s.value).toList();  void _ensureExists(String key) {      _fieldStates[key] ??= FormFieldValidationState(key: key);  }  void dispose() {    _fieldStates.entries.forEach((s) {        s.value.focusNode.dispose();    });  }}class FormFieldValidationState {  final String key;  bool hasError;  FocusNode focusNode;  FormFieldValidationState({@required this.key})      : hasError = false,      focusNode = FocusNode();}

To use it, create your forms as usual, but add a FormValidationManager to your state class, and then use that instance to wrap your validation methods.

Usage:

class _MyWidgetState extends State<MyWidget> {  final _formKey = GlobalKey<FormState>();  final _formValidationManager = FormValidationManager();  @override  Widget build(BuildContext context) {    return Form(      key: _formKey,      child: Column(        children: [          TextFormField(              focusNode: _formValidationManager.getFocusNodeForField('field1'),              validator: _formValidationManager.wrapValidator('field1', (value) {                if (value.isEmpty) {                  return 'Please enter a value';                }                return null;              })),          TextFormField(              focusNode: _formValidationManager.getFocusNodeForField('field2'),              validator: _formValidationManager.wrapValidator('field2', (value) {                if (value.isEmpty) {                  return 'Please enter a value';                }                return null;              })),          ElevatedButton(              onPressed: () {                if (!_formKey.currentState.validate()) {                  _formValidationManager.erroredFields.first.focusNode.requestFocus();                }              },              child: Text('SUBMIT'))        ],      ),    );  }  @override  void dispose() {    _formValidationManager.dispose();    super.dispose();  }}