【问题标题】:How can I validate the value in textformfield using restful call in Flutter?如何在 Flutter 中使用 restful 调用验证 textformfield 中的值?
【发布时间】:2019-10-13 09:38:41
【问题描述】:

我想检查表单中输入的邮政编码是否有效。我正在使用this 进行验证。

我想向用户显示,如下所示,如果他们输入的邮政编码不正确。

Widget formUI() {
  return new Column(
    children: <Widget>[
      new TextFormField(
          decoration: new InputDecoration(hintText: 'Email ID'),
          keyboardType: TextInputType.emailAddress,
          maxLength: 32,
          validator: Validation.validateEmail,
          onSaved: (String val) {
            email = val;
          }),
      new TextFormField(
        decoration: new InputDecoration(hintText: 'Password'),
        maxLength: 32,
        validator: Validation.validateName,
        onSaved: (String val) {
          password = val;
          print("name is now $password from $val");
        },
      ),
      //I WANT TO VALIDATE THE POSTCODE
      new TextFormField(
        decoration: new InputDecoration(hintText: 'Postcode'),
        maxLength: 32,
        validator: checkThePostCode(),
        onSaved: (String val) {
          postcode = val;
          print("name is now $postcode from $val");
        },
      ),
      new SizedBox(height: 15.0),
      new RaisedButton(
        onPressed: () {
          if (_sendToServer()) {
            _showSnackBar("Successfully Signed-up!");
          }
        },
        child: new Text('Sign up'),
      ),
    ],
  );
}

而且,我有下面的代码可以进行 API 调用。我想在表单上显示错误响应,例如“无效的邮政编码”

Future<String> checkThePostCode() async {
  final response =
      await http.get('https://api.postcodes.io/postcodes/$postcode');

  if (response.statusCode != 200) {
    var map = ErrorMessage.fromJson(json.decode(response.body));
    postcodeValidator = map.error;
    return map.error;
  }
  return null;
}

【问题讨论】:

    标签: flutter dart


    【解决方案1】:

    validator 属性面临的问题是您必须同步返回 String,如果邮政编码不正确,将显示该String

    在您的情况下,这是不可能的,因为您需要等待网络调用完成。
    对此的一种解决方案是将TextEditingController 附加到您的TextFormField

    每次输入值时,我都会向 API 发出网络请求。 (这并不理想,您可以在这里设置一个去抖动功能,以免向 API 发送过多垃圾邮件)

    只要所有字段都无效,提交按钮就会被禁用。

    概念证明代码:

    class PostcodeValidator extends StatefulWidget {
      @override
      _PostcodeValidatorState createState() => _PostcodeValidatorState();
    }
    
    class _PostcodeValidatorState extends State<PostcodeValidator> {
      String email;
      String password;
      String postcode;
      bool hasValidPostcode = false;
      bool get canSubmitForm => hasValidPostcode;
      String postcodeErrorMessage;
    
      final TextEditingController _postcodeController = TextEditingController();
    
      void checkPostalCode(String postcode) async {
        final response = await http.get('https://api.postcodes.io/postcodes/$postcode');
    
        if (response.statusCode != 200) {
          setState(() {
            hasValidPostcode = false;
            postcodeErrorMessage = json.decode(response.body)["error"];
          });
        }
    
        if (response.statusCode == 200) {
          setState(() {
            hasValidPostcode = true;
          });
        }
    
        return null;
      }
    
      @override
      void initState() {
        super.initState();
        _postcodeController.addListener(() {
          final postcodeInput = _postcodeController.value.text;
          if (postcodeInput.isNotEmpty) {
            checkPostalCode(postcodeInput);
          }
        });
      }
    
      @override
      Widget build(BuildContext context) => Column(
        children: <Widget>[
          TextFormField(
            controller: _postcodeController,
            decoration: InputDecoration(hintText: 'Postcode'),
            maxLength: 32,
            autovalidate: true,
            validator: (_) => hasValidPostcode ? null : postcodeErrorMessage,
            onSaved: (String val) {
              postcode = val;
              print("name is now $postcode from $val");
            },
          ),
          RaisedButton(
            onPressed: canSubmitForm ? _sendToServer : null,
            child: new Text('Sign up'),
          ),
        ],
      );
    
      void _sendToServer() {
        print("Submitting");
      }
    }
    

    【讨论】:

    • 这真的很好用,但每次击键都会调用 API。有没有办法在点击注册按钮时只能点击一次?
    • 对上述评论有任何更新吗?我真的被这个困住了。
    【解决方案2】:

    您可以使用flutter_form_bloc,它支持异步验证器,并且您可以设置去抖动时间以不向 API 发送垃圾邮件,此外还有其他优势。

    因此,当您在 SignUpFormBloc 中创建 TextFieldBloc 时,您可以设置去抖时间:

    class SignUpFormBloc extends FormBloc<String, String> {
      ...
      final postcodeField = TextFieldBloc(
        asyncValidatorDebounceTime: Duration(milliseconds: 1000),
      );
      ...
    }
    

    然后你可以在构造函数中添加你的异步验证器:

    ...
      SignUpFormBloc() {
        postcodeField.addAsyncValidators([_isValidPostcode]);
      }
    ...
    

    如果您不想在输入时自动验证,并且只在提交表单时,您可以在super 构造函数中将autoValitade 设置为false

    ...
      SignUpFormBloc() : super(autoValidate: false) {
        postcodeField.addAsyncValidators([_isValidPostcode]);
      }
    ...
    

    或者如果你想要自动验证并且只在提交表单时检查异步验证器,你可以检查这个答案:https://stackoverflow.com/a/58295440/11768068

    这是一个可以运行的示例:

    dependencies:
      form_bloc: ^0.5.2
      flutter_form_bloc: ^0.4.4
      http: ^0.12.0+2
    
    import 'dart:async';
    import 'dart:convert';
    import 'package:flutter/material.dart';
    import 'package:form_bloc/form_bloc.dart';
    import 'package:flutter_form_bloc/flutter_form_bloc.dart';
    import 'package:http/http.dart' as http;
    
    void main() => runApp(MaterialApp(home: SignUpScreen()));
    
    class SignUpFormBloc extends FormBloc<String, String> {
      final emailField = TextFieldBloc(
        validators: [Validators.email],
      );
    
      final passwordField = TextFieldBloc(
        validators: [Validators.passwordMin6Chars],
      );
    
      final postcodeField = TextFieldBloc(
        asyncValidatorDebounceTime: Duration(milliseconds: 500),
      );
    
      @override
      List<FieldBloc> get fieldBlocs => [emailField, passwordField, postcodeField];
    
      SignUpFormBloc() {
        postcodeField.addAsyncValidators([_isValidPostcode]);
      }
    
      Future<String> _isValidPostcode(String postcode) async {
        try {
          final response =
              await http.get('https://api.postcodes.io/postcodes/$postcode');
    
          print(response.body);
          if (response.statusCode != 200) {
            return json.decode(response.body)['error'];
          }
        } catch (e) {
          return 'There is no Internet connection';
        }
    
        return null;
      }
    
      @override
      Stream<FormBlocState<String, String>> onSubmitting() async* {
        // Form logic...
        try {
          // Get the fields values:
          print(postcodeField.value);
          print(emailField.value);
          print(postcodeField.value);
          await Future<void>.delayed(Duration(seconds: 2));
          yield currentState.toSuccess();
        } catch (e) {
          yield currentState.toFailure(
              'Fake error, please continue testing the async validation.');
        }
      }
    }
    
    class SignUpScreen extends StatefulWidget {
      SignUpScreen({Key key}) : super(key: key);
    
      _SignUpScreenState createState() => _SignUpScreenState();
    }
    
    class _SignUpScreenState extends State<SignUpScreen> {
      SignUpFormBloc _simpleFormBloc;
    
      @override
      void initState() {
        super.initState();
        _simpleFormBloc = SignUpFormBloc();
      }
    
      @override
      void dispose() {
        _simpleFormBloc.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('Sign up')),
          body: FormBlocListener(
            formBloc: _simpleFormBloc,
            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: _simpleFormBloc.emailField,
                  decoration: InputDecoration(labelText: 'Email ID'),
                  maxLength: 32,
                  keyboardType: TextInputType.emailAddress,
                ),
                TextFieldBlocBuilder(
                  textFieldBloc: _simpleFormBloc.passwordField,
                  decoration: InputDecoration(labelText: 'Password'),
                  maxLength: 32,
                ),
                TextFieldBlocBuilder(
                  textFieldBloc: _simpleFormBloc.postcodeField,
                  suffixButton: SuffixButton.circularIndicatorWhenIsAsyncValidating,
                  decoration: InputDecoration(labelText: 'Postcode'),
                  maxLength: 32,
                  errorBuilder: (context, error) {
                    // Here you can map your error codes
                    // if you want to use Localizations
                    switch (error) {
                      case ValidatorsError.requiredTextFieldBloc:
                        return 'Please enter a postcode.';
                        break;
                      default:
                        return error;
                    }
                  },
                ),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: RaisedButton(
                    onPressed: _simpleFormBloc.submit,
                    child: Center(child: Text('SIGN UP')),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    
    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('Go to home'),
                    onPressed: () => Navigator.of(context).pushReplacement(
                        MaterialPageRoute(builder: (_) => SignUpScreen())),
                  )
                ],
              ),
            ),
          ),
        );
      }
    }
    
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-05-23
      • 1970-01-01
      • 2021-01-19
      • 1970-01-01
      • 2022-01-10
      • 2022-10-21
      • 2019-08-28
      • 2020-10-27
      相关资源
      最近更新 更多