1

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'),
        ),
      ],
    );
  }
}
Carleton Y
  • 289
  • 5
  • 17

4 Answers4

2

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.

Cyrus the Great
  • 5,145
  • 5
  • 68
  • 149
  • I have previously liked answers from you on other SO threads so I know that you are an experienced developer which I am not. I'm a beginner. I have edited my question to try to more explicitly get my question across which is that I want the button to be enabled based on my regex (validateEmail method) and not just whether or not the text form field is empty. Thank you very much for your quick response. – Carleton Y Jan 30 '22 at 22:43
  • It worked with a slight change. To get the behavior I wanted I was able to use your updated code once I changed it to: WidgetsBinding.instance?.addPostFrameCallback((timeStamp) { setState(() { _isValidated = true; }); }); – Carleton Y Feb 01 '22 at 00:20
1

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'),
        ),
CharlyKeleb
  • 587
  • 4
  • 17
  • Thanks for a prompt reply to my question. Your code doesn't account for the validateEmail method so unfortunately it doesn't give me the behavior I am looking for. I appreciate you taking the time to respond. – Carleton Y Jan 30 '22 at 22:25
1

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;
DARTender
  • 424
  • 2
  • 7
  • Thank you for this suggestion. This approach only tests the text form field to determine whether or not it is empty. If the text form field is empty the button is disabled and if it contains at least one character the button is enabled. It doesn't test for the email validation which is contained in my validateEmail method. But thanks very much for the response. I appreciate it. – Carleton Y Jan 30 '22 at 22:20
0

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 **

M.Adnan Ijaz
  • 91
  • 10
  • This will help me with disabling buttons in general which is something that I have wanted to do better. Thank you very much. – Carleton Y Mar 09 '22 at 12:34