【问题标题】:Manually setting Flutter Validation Error手动设置 Flutter 验证错误
【发布时间】:2019-10-04 23:00:39
【问题描述】:

验证表单并从 Flutter 向服务器后端发送请求后:我想将来自服务器的任何潜在错误消息设置为以原始表单显示。最好与验证错误完全一样。

例如:

Widget build(BuildContext context) {
...
  TextFormField(
    onFieldSubmitted: (value) => _signIn(),
    validator: (input) {
      if (input.length < 6)
        return 'Your password is too short';
      return null;
    },
    onSaved: (input) => _password = input,
    decoration: InputDecoration(
      labelText: 'Password',
    ),
    obscureText: true,
  )
...
}

Future<void> _signIn() async {
  final formState = _formKey.currentState;
  if (!formState.validate()) return;
  formState.save();

  try {
    ... // do fancy request stuff
  } catch (e) {
    // this is where I want to set the "validation" error
  }
}

【问题讨论】:

  • 您是否使用 Firebase 作为后端。 (即 Cloud Functions、Firestore 等)?
  • 是的。没想到这很重要。

标签: forms validation flutter dart error-handling


【解决方案1】:

实际上超级简单,验证错误仍然有效。

String? _errorMsg;

Widget build(BuildContext context) {
...
  TextFormField(
    onFieldSubmitted: (value) => _signIn(),
    validator: (input) {
      if (input.length < 6)
        // will set the errorText directly, no need for a variable here
        return 'Your password is too short';
      return null;
    },
    onSaved: (input) => _password = input,
    decoration: InputDecoration(
      labelText: 'Password',
      errorText: _errorMsg,
    ),
    obscureText: true,
  )
...
}

Future<void> _signIn() async {
  setState(() {
    _errorMsg = null; // clear any existing errors
  });

  final formState = _formKey.currentState;
  if (!formState.validate()) return;
  formState.save();

  try {
    ... // do fancy request stuff
  } catch (e) {
    setState(() {
      _errorMsg = 'Wrong password.';
    });
  }
}

【讨论】:

    【解决方案2】:

    我想,我可以想到一个解决方案,但我认为这有点难看。 我可以有一个“错误”变量,在请求失败时设置。 然后我会在那里再次调用 formState.validate():检查错误变量,如果它不为空,则返回它。

    【讨论】:

    • 我想出了同样的事情。我有一个 String validationMessage 变量,其中包含错误文本或 null 否则。在我的TextFormFieldvalidator: (value) =&gt; model.validationMessage 中,我刚刚读到了这个值。同样在我的模型中,我设置了不同的状态,例如“初始、加载、失败、成功”,并且在构建方法中我做出了相应的反应。如果它的“失败”状态 - 我打电话给_formKey.currentState.validate() 用红色边框突出显示它并显示错误消息。 p.s.我使用 Provider 并遵循 MVVM 模式。
    • 请问您可以通过分享您的解决方案代码示例来添加一些内容吗?
    【解决方案3】:

    您可以在 TextFormField 中使用controller 来验证表单并显示服务器错误。

    TextEditingController controllerPassword = new TextEditingController();
    Widget build(BuildContext context) {
    ...
      TextFormField(
        onFieldSubmitted: (value) => _signIn(),
        validator: (input) {
          if (input.length < 6)
            return 'Your password is too short';
          return null;
        },
        onSaved: (input) => _password = input,
        decoration: InputDecoration(
          labelText: 'Password',
        ),
        obscureText: true,
        controller: controllerPassword,
      )
    ...
    }
    
    Future<void> _signIn() async {
      final formState = _formKey.currentState;
      if (!formState.validate()) return;
      formState.save();
    
      try {
        ... // do fancy request stuff
      } catch (e) {
        // this is where I want to set the "validation" error
         setState(() {
            controllerPassword.text = "Server Error"
         }));
      }
    }
    

    更多参考:Flutter Form Validation

    【讨论】:

      【解决方案4】:

      你可以使用flutter_form_blocTextFieldBlocaddError方法。

      usernameField.addError('That username is taken. Try another.');
      

      请记住,您也可以使用异步验证器。

      这是一个完整的例子:

      dependencies:
        flutter:
          sdk: flutter
        flutter_bloc: ^0.21.0
        form_bloc: ^0.5.0
        flutter_form_bloc: ^0.4.1+1
      
      import 'package:flutter/material.dart';
      import 'package:flutter_bloc/flutter_bloc.dart';
      import 'package:flutter_form_bloc/flutter_form_bloc.dart';
      import 'package:form_bloc/form_bloc.dart';
      
      void main() {
        runApp(MaterialApp(home: SignUpForm()));
      }
      
      class SignUpFormBloc extends FormBloc<String, String> {
        final usernameField = TextFieldBloc();
        final passwordField =
            TextFieldBloc(validators: [Validators.passwordMin6Chars]);
      
        @override
        List<FieldBloc> get fieldBlocs => [usernameField, passwordField];
      
        @override
        Stream<FormBlocState<String, String>> onSubmitting() async* {
          // Form logic...
          try {
            await _signUp(
              throwException: true,
              username: usernameField.value,
              password: passwordField.value,
            );
            yield currentState.toSuccess();
          } catch (e) {
            // When get the error from the backend you can
            // add the error to the field:
            usernameField.addError('That username is taken. Try another.');
      
            yield currentState
                .toFailure('The error was added to the username field.');
          }
        }
      
        Future<void> _signUp({
          @required bool throwException,
          @required String username,
          @required String password,
        }) async {
          print(username);
          print(password);
          await Future<void>.delayed(Duration(seconds: 2));
          if (throwException) throw Exception();
        }
      }
      
      class SignUpForm extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return BlocProvider<SignUpFormBloc>(
            builder: (context) => SignUpFormBloc(),
            child: Builder(
              builder: (context) {
                final formBloc = BlocProvider.of<SignUpFormBloc>(context);
      
                return Scaffold(
                  appBar: AppBar(title: Text('Sign Up Form')),
                  body: FormBlocListener<SignUpFormBloc, String, String>(
                    onSubmitting: (context, state) {
                      // Show the progress dialog
                      showDialog(
                        context: context,
                        barrierDismissible: false,
                        builder: (_) => WillPopScope(
                          onWillPop: () async => false,
                          child: Center(
                            child: Card(
                              child: Container(
                                width: 80,
                                height: 80,
                                padding: EdgeInsets.all(12.0),
                                child: CircularProgressIndicator(),
                              ),
                            ),
                          ),
                        ),
                      );
                    },
                    onSuccess: (context, state) {
                      // Hide the progress dialog
                      Navigator.of(context).pop();
                      // Navigate to success screen
                      Navigator.of(context).pushReplacement(
                          MaterialPageRoute(builder: (_) => SuccessScreen()));
                    },
                    onFailure: (context, state) {
                      // Hide the progress dialog
                      Navigator.of(context).pop();
                      // Show snackbar with the error
                      Scaffold.of(context).showSnackBar(
                        SnackBar(
                          content: Text(state.failureResponse),
                          backgroundColor: Colors.red[300],
                        ),
                      );
                    },
                    child: ListView(
                      children: <Widget>[
                        TextFieldBlocBuilder(
                          textFieldBloc: formBloc.usernameField,
                          decoration: InputDecoration(labelText: 'Username'),
                        ),
                        TextFieldBlocBuilder(
                          textFieldBloc: formBloc.passwordField,
                          decoration: InputDecoration(labelText: 'Password'),
                        ),
                        Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: RaisedButton(
                            onPressed: formBloc.submit,
                            child: Center(child: Text('SUBMIT')),
                          ),
                        ),
                      ],
                    ),
                  ),
                );
              },
            ),
          );
        }
      }
      
      class SuccessScreen extends StatelessWidget {
        const SuccessScreen({Key key}) : super(key: key);
      
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            backgroundColor: Colors.green[300],
            body: Center(
              child: SingleChildScrollView(
                child: Column(
                  children: <Widget>[
                    Icon(
                      Icons.sentiment_satisfied,
                      size: 100,
                    ),
                    RaisedButton(
                      color: Colors.green[100],
                      child: Text('Sign out'),
                      onPressed: () => Navigator.of(context).pushReplacement(
                          MaterialPageRoute(builder: (_) => SignUpForm())),
                    )
                  ],
                ),
              ),
            ),
          );
        }
      }
      
      

      【讨论】:

        【解决方案5】:

        一个简单的解决方案:

        为小部件状态创建一个键:

        GlobalKey<_CustomErrorTextField> _passwordTextFieldState = GlobalKey();
        

        使用键设置错误信息:

        _passwordTextFieldState.currentState.updateError(errorMsg);
        

        2秒后重置错误:

        Future.delayed(Duration(seconds: 2), () {
           // Runs after duration sec
           _passwordTextFieldState.currentState.updateError(null);
        });
        

        设置小部件(一定要设置密钥):

        CustomErrorTextField(
           key: _passwordTextFieldState,
           label: "Password",
           currentPassword: password,
           validator: yourValidator,
           callback: passwordCallback,
           obscureText: hidePassword.value - a bool value show/hide password
        )
        

        这里是小部件:

        class CustomErrorTextField extends StatefulWidget {
        
          CustomErrorTextField({
            Key key,
            this.label,
            this.currentPassword,
            this.validator,
            this.callback,
            this.obscureText = false
          }): super(key: key);
        
          final String label;
          final String currentPassword;
          final FormFieldValidator<String> validator;
          final Function callback;
          final obscureText;
        
          @override
          _CustomErrorTextField createState() => _CustomErrorTextField();
        }
        
        class _CustomErrorTextField extends State<CustomErrorTextField> {
        
          String errorMsg;
        
          updateError(String errorMsg){
            setState(() {
              this.errorMsg = errorMsg;
            });
          }
        
          @override
          Widget build(BuildContext context) {
            return TextFormField(
              decoration: InputDecoration(
                  labelText: widget.label,
                  errorText: errorMsg
              ),
              initialValue: widget.currentPassword,
              keyboardType: TextInputType.visiblePassword,
              validator: widget.validator,
              onSaved: (String val) {
                widget.callback(val);
              },
              obscureText: widget.obscureText,
            );
          }
        }
        

        【讨论】:

          猜你喜欢
          • 2011-07-02
          • 2017-06-08
          • 1970-01-01
          • 2012-09-22
          • 2015-08-24
          • 2013-11-10
          • 2011-10-22
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多