Flutter:根据验证更改状态

Sam*_*ham 5 mobile dart material-design flutter

我正在为演示应用程序构建一个简单的忘记密码表单,该表单由一个TextFormFields和一个组成,FloatingActionButton用于提交数据。我已经意识到FloatingActionButton没有禁用布尔状态,所以我想尝试通过_isValid: true/ false根据TextFormField验证函数更改状态来复制它,然后我可以放置一些三元运算符FloatingActionButton来更改颜色和功能,取决于此小部件的状态。

您将能够看到我_autoValidate在挂载小部件时设置为 true,然后我尝试在_validateForgetEmail函数中触发 UI 重新加载。当我触发这些状态更改时,我收到一个很大的 UI 错误说

??? EXCEPTION CAUGHT BY WIDGETS LIBRARY ????????????????????????????????????????????????????????????
flutter: The following assertion was thrown building Form-[LabeledGlobalKey<FormState>#0a40e](dirty, state:
flutter: FormState#59216):
flutter: setState() or markNeedsBuild() called during build.
flutter: This ForgotPasswordForm widget cannot be marked as needing to build because the framework is already
flutter: in the process of building widgets. A widget can be marked as needing to be built during the build
flutter: phase only if one of its ancestors is currently building. This exception is allowed because the
flutter: framework builds parent widgets before children, which means a dirty descendant will always be
flutter: built. Otherwise, the framework might not visit this widget during this build phase.
Run Code Online (Sandbox Code Playgroud)

代码如下:

class ForgotPasswordForm extends StatefulWidget {
  @override
  _ForgotPasswordFormState createState() => _ForgotPasswordFormState();
}
Run Code Online (Sandbox Code Playgroud)
Class _ForgotPasswordFormState extends State<ForgotPasswordForm> {
  final _emailController = TextEditingController();
  final _formKey = GlobalKey<FormState>();
  final bool _autoValidate = true;

  bool _isLoading = false;
  bool _isValid = false;

  String email;

  @override
  Widget build(BuildContext context) {
    // Build a Form widget using the _formKey created above.
    return Form(
      key: _formKey,
      child: _isLoading
          ? _buildLoadingSpinner(context)
          : _buildPasswordForm(context),
      autovalidate: _autoValidate,
    );
  }

  Widget _buildLoadingSpinner(BuildContext context) {
    return (Center(child: CircularProgressIndicator()));
  }

  Widget _buildPasswordForm(BuildContext context) {
    print('isValid: ' + _isValid.toString());

    return Column(
      children: <Widget>[
        Text(
          'Please enter your email address.',
          style: TextStyle(fontSize: 14.0),
          textAlign: TextAlign.center,
        ),
        Text(
          'You will recieve a link to reset your password.',
          style: TextStyle(fontSize: 14.0),
          textAlign: TextAlign.center,
        ),
        SizedBox(height: 32.0),
        TextFormField(
          controller: _emailController,
          validator: _validateForgetEmail,
          keyboardType: TextInputType.emailAddress,
          autovalidate: _autoValidate,
          style: TextStyle(fontSize: 14.0),
          onSaved: (String val) {
            email = val;
          },
          decoration: InputDecoration(
            filled: true,
            contentPadding: EdgeInsets.symmetric(horizontal: 15, vertical: 8),
            labelText: 'Email',
            border: InputBorder.none,
            labelStyle: TextStyle(fontSize: 14.0, color: Colors.lightBlueAccent),
            errorStyle: TextStyle(fontSize: 10.0, height: 0.5),
            focusedBorder: UnderlineInputBorder(
              borderSide: BorderSide(color: Colors.lightGreenAccent, width: 2.0),
            ),
          ),
        ),
        SizedBox(height: 24.0),
        FloatingActionButton(
          backgroundColor: _isValid ? Colors.lightBlue : Colors.grey,
          onPressed: () {
            _submitPasswordReset();
          },
          child: Icon(Icons.arrow_forward_ios, size: 14.0),
        )
      ],
      mainAxisAlignment: MainAxisAlignment.center,
    );
  }

  void _submitPasswordReset() async {
    if (_formKey.currentState.validate()) {
      setState(() {
        _isLoading = true;
      });

      UserPasswordResetRequest newPasswordRequest =
          new UserPasswordResetRequest(email: _emailController.text);

      http.Response response = await ApiService.queryPost(
          '/api/users/password-forgot',
          body: newPasswordRequest.toJson());

      final int statusCode = response.statusCode;

      if (statusCode == 400) {
        Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Wrong email or password'),
            duration: Duration(seconds: 3),
            backgroundColor: Colors.red));

        setState(() {
          _isLoading = false;
        });
      }

      if (statusCode == 200) {
        // setState(() {
        //   _isLoading = false;
        // });

        Navigator.push(
          context,
          MaterialPageRoute(builder: (context) => UserBackToLogin()),
        );
      }

      setState(() {
        _isLoading = false;
      });
    }
  }

  String _validateForgetEmail(String value) {
    String patttern =
        r"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$";
    RegExp regExp = new RegExp(patttern);
    if (value.length == 0) {
      return "Email is Required";
    } else if (!regExp.hasMatch(value)) {
      setState(() {
        _isValid = false;
      });

      return "Must be a valid email address";
    }

    print('value' + value);

    setState(() {
      _isValid = true;
    });

    return null;
  }
}
Run Code Online (Sandbox Code Playgroud)

任何见解都会很高兴看到我做错了什么 - 对颤振非常陌生。如果您需要更多信息,那么我可以提供。

干杯山姆

Mik*_*kin 8

:你可以做这样的
拆分_validateForgetEmail两个方法:

String _validateForgetEmail(String value) {
  if (value.length == 0) {
    return "Email is Required";
  } else if (!_isEmailValid(value)) {
    return "Must be a valid email address";
  }

  print('value' + value);

  return null;
}

bool _isEmailValid(String value) {
  String pattern =
      r"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$";
  RegExp regExp = new RegExp(pattern);

  return regExp.hasMatch(value);
}
Run Code Online (Sandbox Code Playgroud)

现在这些方法只验证值而不影响任何状态。

聆听_emailController变化

@override
void initState() {
  super.initState();
  _emailController.addListener(() {
    final isEmailValid = _isEmailValid(_emailController.value.text);
    if(isEmailValid != _isValid) {
      setState(() {
        _isValid = isEmailValid;
      });
    }
  });
}
Run Code Online (Sandbox Code Playgroud)

也不要忘记处置 _emailController

@override
void dispose() {
  _emailController.dispose();
  super.dispose();
}
Run Code Online (Sandbox Code Playgroud)

异常解释:
TextFormField扩展FormField类。如果autovalidate打开,则传递 as 的函数validator将在FormFieldState.build方法中调用以更新错误文本。
所以它导致setState被调用,build这是框架不允许的


szo*_*otp 6

实现此目的的更简单方法是在onChanged回调中进行验证。

class FormPage extends StatefulWidget {
  @override
  _FormPageState createState() => _FormPageState();
}

class _FormPageState extends State<FormPage> {
  final _formKey = GlobalKey<FormState>();

  bool _isValid = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Form(
        key: _formKey,
        onChanged: () {
          final isValid = _formKey.currentState.validate();
          if (_isValid != isValid) {
            setState(() {
              _isValid = isValid;
            });
          }
        },
        child: Column(
          children: <Widget>[
            TextFormField(validator: (x) => x.length > 2 ? null : 'Too short'),
          ],
        ),
      ),
      floatingActionButton: Opacity(
        opacity: _isValid ? 1 : 0.5,
        child: FloatingActionButton(
          child: Icon(Icons.send),
          onPressed: () {},
        ),
      ),
    );
  }
}

Run Code Online (Sandbox Code Playgroud)


Ank*_*hra 6

使用简单

Form(
   key: _key,
   autovalidateMode: AutovalidateMode.onUserInteraction,
   child: Column(
      children: [
         //All FormTextFields Here as you went....
      ],
   ),
),
Run Code Online (Sandbox Code Playgroud)