简体   繁体   English

Flutter中TextFormField的错误信息如何清除

[英]How to clear error message in TextFormField in Flutter

In my code I validate phone number.在我的代码中,我验证了电话号码。 If phone number is incorrect - I show error message.如果电话号码不正确 - 我会显示错误消息。 But, when user starts to edit number I want to hide this error message.但是,当用户开始编辑号码时,我想隐藏此错误消息。

I've found the solution with currentState.reset() , but it seems not the good one.我找到了currentState.reset()的解决方案,但它似乎不是很好的解决方案。 I have to handle issues with saving text and cursor position. And I still have one small artifact.我必须处理保存文本和 cursor position 的问题。而且我还有一件小神器。 Normally when I press and hold backspace - it deletes symbols one by one.通常当我按住退格键时 - 它会一个一个地删除符号。 If I do it when error message is shown - then error message disappears and only one symbol is deleted.如果我在显示错误消息时这样做 - 那么错误消息就会消失并且只会删除一个符号。

Does anybody know the right solution for this case?有人知道这种情况的正确解决方案吗?

final TextEditingController controller = TextEditingController();
final RegExp _phoneRegex = RegExp(r"^\+{1}\d{10, 15}\$");
bool isError = false;
TextSelection currentPosition;

return Column(
  children: <Widget>[
    Form(
        key: _textKey,
        child: TextFormField(
          controller: controller,
          validator: (str) {
            isError = true;
            if (str.isEmpty) {
              return err_empty_field;
            } else if (!_phoneRegex.hasMatch(str)) {
              return err_invalid_phone;
            }
            isError = false;
          },
        ),
        onChanged: () {
          if (controller.selection.start < 0 &&
              controller.text.length > 0) {
            TextSelection position =
                controller.text.length > currentPosition.start
                    ? currentPosition
                    : TextSelection.fromPosition(
                        TextPosition(offset: controller.text.length));
            controller.selection = position;
          }
          if (isError) {
            isError = false;
            currentPosition = controller.selection;
            if (currentPosition.start > controller.text.length) {
              currentPosition = TextSelection.fromPosition(
                  TextPosition(offset: controller.text.length));
            }
            String currentText = controller.text;
            _textKey.currentState.reset();
            controller.text = currentText;
            controller.selection = currentPosition;
          }
        },
      ),
    RaisedButton(
      onPressed: () {
        _textKey.currentState.validate();
      },
      child: Text(login),
    )
  ],
);

EDIT (Nov 2020)编辑(2020 年 11 月)

autovalidate was deprecated after v1.19.0. autovalidate在 v1.19.0 之后被弃用。
Instead use autovalidateMode :而是使用autovalidateMode

Form(
  autovalidateMode: AutovalidateMode.onUserInteraction`.
  ...
)

Original post原帖

here is a suitable solution to this problem.这是解决此问题的合适方法。

You don't actually need to use onChanged or any tips causing side-effects, I solved it by creating a class property which is initialized to false :您实际上不需要使用onChanged或任何导致副作用的提示,我通过创建一个初始化为false的类属性来解决它:

bool _autovalidate = false;

The Form Widget has a named property autovalidate . Form Widget 有一个命名属性autovalidate You should pass it the previous boolean:您应该将其传递给前一个布尔值:

Form(
  key: _textKey,
  autovalidate: _autovalidate,
  ... 
)

And in your Submit button onPressed() method, you should update the _autovalidate boolean to be true if the form is invalid , this will make the form to auto validate the TextFormField on every onChanged call:在提交按钮的onPressed()方法中,如果表单invalid ,您应该将_autovalidate布尔值更新为true ,这将使表单在每次onChanged调用时自动验证 TextFormField:

RaisedButton(
  onPressed: () {
    if (_textKey.currentState.validate()) {
      print('valid');
    } else {
      print('invalid');
      setState(() => _autoValidate = true);
    }
  },
  child: Text(login),
)

I hope it helped Somebody.我希望它能帮助某人。

January 2021 2021 年 1 月

...

AutovalidateMode _autoValidate = AutovalidateMode.disabled;
Form(
  key: _textKey,
  autovalidateMode: _autovalidate,
  ... 
)
RaisedButton(
  onPressed: () {
    if (_textKey.currentState.validate()) {
      print('valid');
    } else {
      print('invalid');
      setState(() => _autoValidate = AutovalidateMode.always);
    }
  },
  child: Text("login"),
)

The problem here is errorText is automatically managed by the validator field of the TextFormField .这里的问题是errorTextTextFormFieldvalidator字段自动管理 At the same time, the simple solution is to handle the errorText manually .同时,简单的解决方案是手动处理errorText

Step 1: Create第 1 步:创建

  • String field, _errorText initialised to null .字符串字段, _errorText初始化为null The field will hold the error message that needs to be shown.该字段将包含需要显示的错误消息。
  • Boolean field, _error initialised to false .布尔字段, _error初始化为false The filed is true if there is an error otherwise false .如果有错误则 filed 为true否则为false

Step 2:第2步:

  • Assign _errorText to TextFormField_errorText分配给TextFormField

Step 3 (Important):第 3 步(重要):

  • Make sure that TextFormField validator returns a null value.确保TextFormField validator返回null值。

  • Handle the validation here and assign the proper error message to _errorText .在此处处理验证并将正确的错误消息分配给_errorText

  • Update _error state correspondingly.相应地更新_error状态。

Step 4 (Important):第 4 步(重要):

  • Reset _errorText and _error .重置_errorText_error This will remove the error from field soon as you start editing.这将在您开始编辑时立即从字段中删除错误。

Step 5:第 5 步:

  • Trigger field validation in the onFieldSubmitted and manage your code flow...onFieldSubmitted中触发字段验证并管理您的代码流...
import 'package:flutter/material.dart';

class WorkGround extends StatefulWidget {
  @override
  _WorkGroundState createState() => _WorkGroundState();
}

class _WorkGroundState extends State<WorkGround> {
  final _formKey = GlobalKey<FormState>();
  final _usernameFocusNode = FocusNode();
  final _phoneNumberFocusNode = FocusNode();

  /*
  * Step 1.
  * */
  String _userNameErrorText;
  bool _userNameError = false;
  String _phoneNumberErrorText;
  bool _phoneNumberError = false;

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            TextFormField(
              focusNode: _usernameFocusNode,
              decoration: InputDecoration(
                labelText: 'Username',
                /*
                * Step 2
                * */
                errorText: _userNameErrorText, // Handling error manually
              ),
              textInputAction: TextInputAction.next,
              /*
              * Step 3
              * */
              validator: (value) {
                setState(() {
                  if(value.isEmpty) {
                    _userNameError = true;
                    _userNameErrorText = 'Enter Username';
                  }
                });
                return null; // Return null to handle error manually.
              },
              /*
              * Step 4
              * */
              onChanged: (value) {
                setState(() {
                  _userNameError = false;
                  _userNameErrorText = null; // Resets the error
                });
              },
              /*
              * Step 5
              * */
              onFieldSubmitted: (value) {
                _formKey.currentState.validate(); // Trigger validation
                if(!_userNameError) {
                  FocusScope.of(context).requestFocus(_phoneNumberFocusNode);
                }
              },
            ),
            TextFormField(
              focusNode: _phoneNumberFocusNode,
              decoration: InputDecoration(
                labelText: 'Phone Number',
                /*
                * Step 2
                * */
                errorText: _phoneNumberErrorText, // Handling error manually
              ),
              textInputAction: TextInputAction.done,
              /*
              * Step 3
              * */
              validator: (value) {
                setState(() {
                  if(value.isEmpty) {
                    _phoneNumberError = true;
                    _phoneNumberErrorText = 'Enter Phone number';
                  } else if( value.length < 10) {
                    _phoneNumberError = true;
                    _phoneNumberErrorText = 'Invalid Phone number';
                  }
                });
                return null; // Return null to handle error manually.
              },
              /*
              * Step 4
              * */
              onChanged: (value) {
                setState(() {
                  _phoneNumberError = false;
                  _phoneNumberErrorText = null; // Resets the error
                });
              },
              /*
              * Step 5
              * */
              onFieldSubmitted: (value) {
                _formKey.currentState.validate(); // Trigger validation
                if(!_phoneNumberError) {
                  // submit form or whatever your code flow is...
                }
              },
            ),
          ],
        ),
      ),
    );
  }
}

I have achieved your both below functionality:我已经实现了以下两个功能:

1) Hide error message when editing 1)编辑时隐藏错误信息

2) validate input field when login button pressed 2)按下登录按钮时验证输入字段

Note: i have commented phone number regex and put validation for string length < 10 digit for testing.注意:我已经评论了电话号码正则表达式并验证了字符串长度 < 10 位以进行测试。

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();

}

class _MyHomePageState extends State<MyHomePage> {

  @override
  void initState(){
    super.initState();
  }

  final TextEditingController controller = TextEditingController();
//  final RegExp _phoneRegex = RegExp(r"^\+{1}\d{10, 15}\$");
  bool isError = false;
  bool isWriting = false;
  bool isLoginPressed = false;
  int counter = 0;
  String myErrorString = "";
  TextSelection currentPosition;
  final _textKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext ctx) {
    return Scaffold(
      appBar: AppBar(
        title: Text('MapSample'),
      ),
      body: Container(
        child: Column(
          children: <Widget>[
            Form(
              key: _textKey,
              child: TextFormField(
                controller: controller,
                validator: (str) {
                  myErrorString = "";
                  if(isLoginPressed){
                    isError = true;
                    if (str.isEmpty) {
                      myErrorString = 'err_empty_field';
                      return myErrorString;
                    }
                    else if (str.length < 10) {
                      myErrorString = 'err_invalid_phone';
                      validateMe();
                      return myErrorString;
                    }
                    /*else if (!_phoneRegex.hasMatch(str)) {
                      myErrorString = 'err_invalid_phone';
                      validateMe();
                      return myErrorString;
                    }*/
                    isError = false;
                    myErrorString = "";
                  }else{
                    myErrorString = "";
                  }
                },
              ),

              onChanged: () {
                counter++;
                if(counter == 9){
                  counter = 0;
                  isLoginPressed = false;
                }
                if(isLoginPressed){

                }else{
                  isWriting = true;
                  isLoginPressed = false;
                  myErrorString = "";
                  _textKey.currentState.validate();
                }
              },

            ),
            RaisedButton(
              onPressed: () {
                counter = 1;
                isWriting = false;
                isLoginPressed = true;
                _textKey.currentState.validate();
              },
              child: Text('login'),
            )
          ],
        ),
      ),
    );
  }

  void validateMe() {
    if(isLoginPressed){
      currentPosition = TextSelection.fromPosition(
          TextPosition(offset: controller.text.length));
      String currentText = controller.text;
      _textKey.currentState.reset();
      controller.text = currentText;
      controller.selection = currentPosition;
      isWriting = false;
      isLoginPressed = true;
    }
  }

}

I've found working and easier way我找到了工作和更简单的方法

final _textKey = GlobalKey<FormState>();
final TextEditingController _controller = TextEditingController();

Widget _getPhoneInputForm() {
  final RegExp _phoneRegex = RegExp(r"^\+{1}\d{10,17}");
  bool isError = false;
  bool isButtonPressed = false;

  return Column(
    crossAxisAlignment: CrossAxisAlignment.center,
    mainAxisSize: MainAxisSize.max,
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Padding(
        padding: EdgeInsets.symmetric(horizontal: 36.0),
        child: Form(
          key: _textKey,
          child: TextFormField(
            keyboardType: TextInputType.phone,
            decoration: InputDecoration(
                hintText: hint_enter_phone,
                contentPadding: EdgeInsets.all(24.0),
                fillColor: Colors.blueGrey.withOpacity(0.3),
                filled: true,
                border: OutlineInputBorder(
                    borderRadius: BorderRadius.all(Radius.circular(16.0)),
                    borderSide: BorderSide(color: Colors.blueGrey))),
            controller: _controller,
            validator: (str) {
              if (!isButtonPressed) {
                return null;
              }
              isError = true;
              if (str.isEmpty) {
                return err_empty_field;
              } else if (!_phoneRegex.hasMatch(str)) {
                return err_invalid_phone;
              }
              isError = false;
            },
            onFieldSubmitted: (str) {
              if (_textKey.currentState.validate()) _phoneLogin();
            },
          ),
          onChanged: () {
            isButtonPressed = false;
            if (isError) {
              _textKey.currentState.validate();
            }
          },
        ),
      ),
      RaisedButton(
        color: Colors.teal,
        textColor: Colors.white,
        onPressed: () {
          isButtonPressed = true;
          if (_textKey.currentState.validate()) _phoneLogin();
        },
        child: Text(login),
      )
    ],
  );
}

This is an exemple, i think its not necessary to do onchange(), the function validate name do the work...这是一个例子,我认为它没有必要做 onchange(),函数验证名称完成工作......

 String validateName(String value) {
    String patttern = r'(^[a-zA-Z ]*$)';
    RegExp regExp = new RegExp(patttern);
    if (value.length == 0) {
      return "Name is Required";
    } else if (!regExp.hasMatch(value)) {
      return "Name must be a-z and A-Z";
    }
    return null;
  }

  TextFormField(
                              controller: _lastname, validator: validateName ,
                              //initialValue: widget.contact.last_name,
                              decoration:
                                  InputDecoration(labelText: 'Last name'),
                            ),

void Save() {
 if (_keyForm.currentState.validate()) {
      // No any error in validation
      _keyForm.currentState.save(); 
................
}

i have found that using a combination of FocusNode and AtuoValidateMode.onUserInteraction does the trick.我发现结合使用FocusNodeAtuoValidateMode.onUserInteraction可以达到目的。


class _TextAutoValidateModeExampleState extends State<TextAutoValidateModeExample> {
  FocusNode node = FocusNode();
  @override
  Widget build(BuildContext context) {
    return Container(
      child: TextFormField(
        focusNode: node,
        autovalidateMode: AutovalidateMode.onUserInteraction,
        validator: (value) {
          if(node.hasFocus) return null;
          if (value!.isEmpty) return "value cannot be empty";
          if (!value.isEmail) return "not a valid email";
        },
      ),
    );
  }
}
// Call this method inside onChanged() and when its focusnode hasFocus
void formReset(GlobalKey<FormState> formKey, TextEditingController controller) {
  String stringValue = controller.text;
  TextPosition textPosition = controller.selection.base;

  formKey.currentState.reset();
  controller.text = stringValue;
  controller.selection = TextSelection.fromPosition(textPosition);
}

this format worked for me, Hope it helps someone....这种格式对我有用,希望它能帮助别人....

validator: (value){
bool emailValid = RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+").hasMatch(value);
                          
           isError = true;
           if(value.isEmpty){
                             return "Provide an email";
            }else if(!emailValid){
                return "Enter a valid email";
                              
                   }
                   isError = false;
                            return null;
                        },

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM