'How to disable button until text form field has valid data Flutter

What I want to do is disable the elevated button until the text form field is valid. And then once the data is valid the elevated button should be enabled. I have reviewed several SO threads and a few articles on Google about how to disable a button until the text form field is validated. They all focused on whether or not the text form field was empty or not which is not what I am asking about here. I'm using regex to determine if the user has entered a valid email address. Only when the data entered is a valid email is the data considered valid. That is when I want the button to become enabled. If I try to call setState with a boolean in the validateEmail method I get the error:

setState() or markNeedsBuild() called during build.

Any help will be appreciated. Thank you.

class ResetPasswordForm extends StatefulWidget {
  const ResetPasswordForm({Key? key}) : super(key: key);

  @override
  _ResetPasswordFormState createState() => _ResetPasswordFormState();
}

class _ResetPasswordFormState extends State<ResetPasswordForm> {
  final _formKey = GlobalKey<FormState>();
  final TextEditingController _emailController = TextEditingController();

  String? validateEmail(String? value) {
    String pattern = ValidatorRegex.emailAddress;
    RegExp regex = RegExp(pattern);
    if (value == null || value.isEmpty || !regex.hasMatch(value)) {
      return ValidatorString.enterValidEmail;
    } else {
      return null;
    }
  }

  @override
  void dispose() {
    _emailController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Form(
          key: _formKey,
          child: TextFormField(
            controller: _emailController,
            validator: (value) => validateEmail(value),
          ),
        ),
        ElevatedButton(
          onPressed: () {
            if (_formKey.currentState!.validate()) {
              Auth().resetPassword(
                context,
                _emailController.text.trim(),
              );
            }
          },
          child: const Text('Reset Password'),
        ),
      ],
    );
  }
}


Solution 1:[1]

you can do somthing like that:

class ResetPasswordForm extends StatefulWidget {
  const ResetPasswordForm({Key? key}) : super(key: key);

  @override
  _ResetPasswordFormState createState() => _ResetPasswordFormState();
}

class _ResetPasswordFormState extends State<ResetPasswordForm> {
  final _formKey = GlobalKey<FormState>();
  final TextEditingController _emailController = TextEditingController();
 final bool _isValidated = false;

  String? validateEmail(String? value) {
    String pattern = ValidatorRegex.emailAddress;
    RegExp regex = RegExp(pattern);
    if (value == null || value.isEmpty || !regex.hasMatch(value)) {
      return ValidatorString.enterValidEmail;
    } else {
      setState(){
        _isValidated = true;
      }
      return null;
    }
  }

  @override
  void dispose() {
    _emailController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Form(
          key: _formKey,
          child: TextFormField(
            controller: _emailController,
            validator: (value) => validateEmail(value),
          ),
        ),
        ElevatedButton(
          onPressed:_isValidated
              ? () {
                  //do stuff
                }
              : null,,
          child: const Text('Reset Password'),
        ),
      ],
    );
  }
}
    

if onPressed be null, the button is disabled.

Solution 2:[2]

Enabling and disabling functionality is the same for most widgets

set onPressed property as shown below

onPressed : null returns a disabled widget while onPressed: (){} or onPressed: _functionName returns enabled widget

in this case it'll be this way:

ElevatedButton(
          onPressed: () {
            if (_formKey.currentState!.validate()) {
              Auth().resetPassword(
                context,
                _emailController.text.trim(),
              );
            } else {
              print('disabled');
            }
          },
          child: const Text('Reset Password'),
        ),

Solution 3:[3]

First, move the logic into a named function

void _sendData (){
 if (_formKey.currentState!.validate()) { 
  Auth().resetPassword( context, 
  _emailController.text.trim(), );
}

Now in onpressed

onpressed: _emailController.text.trim.isNotEmpty?_sendData : null;

Solution 4:[4]

Best for this is to just create a form key for this

final formGlobalKey = GlobalKey <FormState> ();

Assign it to form like:

Form(
    key: formGlobalKey,

And now you just have to check the validation for this like:

ElevatedButton(
        style: style,
        onPressed: formGlobalKey.currentState==null?null: formGlobalKey.currentState!.validate()? () {
This is body of button} : null,
            child: const Text('Enabled'),
          ),

**If you didn't use first condition (formGlobalKey.currentState==null?) it will take you towards null exception **

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 CharlyKeleb
Solution 3 O'neya
Solution 4 M.Adnan Ijaz