在Flutter中创建异步验证器

时间:2019-03-26 15:17:40

标签: asynchronous dart flutter

我在Flutter中有一个异步函数,该函数将验证程序的值作为参数:

validatePhone(number) {
  bool _isValid;

  Requests.get("http://apilayer.net/api/validate?value=$number", json: true)
      .then((val) {
    if (val['valid']) {
      // setState(() {  <- also tried setting state here
      _isValid = true;
      // });
    } else {
      // setState(() {
      _isValid = false;
      // });
    }
  });

  return _isValid;
}

TextFormField(
  validator: (value) {
    if (value.isEmpty) {
      return 'Please your phone number';
    } else {
      if (validatePhone(value)) {
        return 'Your phone number is not valid';
      }
    }
  },
),

但不起作用,它总是返回null或在validatePhone中设置的初始值。知道如何使这项工作有效吗?

2 个答案:

答案 0 :(得分:0)

签出flutter_form_bloc,它支持异步验证器,除了提供其他优点外,您还可以设置反跳时间。

您可以在不使用TextFieldBloc的情况下使用FormBloc,但是如果在FormBloc内使用,则功能更加强大

...
    _phoneFieldBloc = TextFieldBloc(
      asyncValidatorDebounceTime: Duration(milliseconds: 300),
      asyncValidators: [_validatePhone],
    );
...
...
     TextFieldBlocBuilder(
       textFieldBloc: _phoneFieldBloc,
       suffixButton: SuffixButton.circularIndicatorWhenIsAsyncValidating,
       decoration: InputDecoration(labelText: 'Phone number'),
       keyboardType: TextInputType.phone,
     ),
...

示例1-不使用FormBloc

dependencies:
  form_bloc: ^0.5.2
  flutter_form_bloc: ^0.4.3
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:form_bloc/form_bloc.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';

void main() => runApp(MaterialApp(home: HomeScreen()));

class HomeScreen extends StatefulWidget {
  HomeScreen({Key key}) : super(key: key);

  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  TextFieldBloc _phoneFieldBloc;

  StreamSubscription<TextFieldBlocState> _textFieldBlocSubscription;

  @override
  void initState() {
    super.initState();
    _phoneFieldBloc = TextFieldBloc(
      asyncValidatorDebounceTime: Duration(milliseconds: 300),
      asyncValidators: [_validatePhone],
    );

    _textFieldBlocSubscription = _phoneFieldBloc.state.listen((state) {
      if (state.isValid) {
        // Print the value of the _textFieldBloc when has a valid value
        print(state.value);
      }
    });
  }

  @override
  void dispose() {
    _phoneFieldBloc.dispose();
    _textFieldBlocSubscription.cancel();
    super.dispose();
  }

  Future<String> _validatePhone(String number) async {
    // Fake phone async validator
    await Future<void>.delayed(Duration(milliseconds: 200));
    if (number.length > 4 && number.length < 9) {
      return 'Your phone number is not valid';
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: TextFieldBlocBuilder(
          textFieldBloc: _phoneFieldBloc,
          suffixButton: SuffixButton.circularIndicatorWhenIsAsyncValidating,
          decoration: InputDecoration(labelText: 'Phone number'),
          keyboardType: TextInputType.phone,
        ),
      ),
    );
  }
}

示例2-使用FormBloc

dependencies:
  form_bloc: ^0.5.2
  flutter_form_bloc: ^0.4.3
import 'package:flutter/material.dart';
import 'package:form_bloc/form_bloc.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';

void main() => runApp(MaterialApp(home: HomeScreen()));

class SimpleFormBloc extends FormBloc<String, String> {
  final phoneField = TextFieldBloc(
    asyncValidatorDebounceTime: Duration(milliseconds: 600),
  );

  final emailField = TextFieldBloc(
    validators: [Validators.email],
    asyncValidatorDebounceTime: Duration(milliseconds: 300),
  );

  @override
  List<FieldBloc> get fieldBlocs => [phoneField, emailField];

  SimpleFormBloc() {
    phoneField.addAsyncValidators([_isValidPhone]);
    emailField.addAsyncValidators([_isEmailAvailable]);
  }

  Future<String> _isValidPhone(String number) async {
    // Fake phone async validator
    await Future<void>.delayed(Duration(milliseconds: 200));
    if (number.length > 4 && number.length < 9) {
      return 'Your phone number is not valid';
    }
    return null;
  }

  Future<String> _isEmailAvailable(String email) async {
    // Fake email async validator
    await Future<void>.delayed(Duration(milliseconds: 200));
    if (email == 'name@domain.com') {
      return 'That email is taken. Try another.';
    } else {
      return null;
    }
  }

  @override
  Stream<FormBlocState<String, String>> onSubmitting() async* {
    // Form logic...
    try {
      // Get the fields values:
      print(phoneField.value);
      print(emailField.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 HomeScreen extends StatefulWidget {
  HomeScreen({Key key}) : super(key: key);

  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  SimpleFormBloc _simpleFormBloc;

  @override
  void initState() {
    super.initState();
    _simpleFormBloc = SimpleFormBloc();
  }

  @override
  void dispose() {
    _simpleFormBloc.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      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.phoneField,
              suffixButton: SuffixButton.circularIndicatorWhenIsAsyncValidating,
              decoration: InputDecoration(labelText: 'Phone number'),
              keyboardType: TextInputType.phone,
            ),
            TextFieldBlocBuilder(
              textFieldBloc: _simpleFormBloc.emailField,
              suffixButton: SuffixButton.circularIndicatorWhenIsAsyncValidating,
              decoration: InputDecoration(labelText: 'Email'),
              keyboardType: TextInputType.emailAddress,
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                onPressed: _simpleFormBloc.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('Go to home'),
                onPressed: () => Navigator.of(context).pushReplacement(
                    MaterialPageRoute(builder: (_) => HomeScreen())),
              )
            ],
          ),
        ),
      ),
    );
  }
}

答案 1 :(得分:-2)

正如评论中所说,不可能有异步验证器,因为validator应该returnString,而不是'Future'。

但是,您的代码中有很多错误。首先,validatePhone在设置_isValid之前返回,这就是为什么要获取null值的原因,因为它从未设置为任何值。您的请求在 之后完成。validatePhone返回,此时设置_isValid是无用的。

让我们尝试修复validatePhone:

Future<bool> validatePhone(number) async {
  bool _isValid;

  final val = await Requests.get(
          "http://apilayer.net/api/validate?value=$number",
          json: true);

  if (val['valid']) {
    // setState(() {
      _isValid = true;
    // });
  } else {
    // setState(() {
      _isValid = false;
    // });
  }

  return _isValid;
}

如您所见,它的返回值必须变为Future<bool>,而不是bool。无法解决此问题。如果允许validator返回Future,则可以正常工作。

您将不得不以一种痛苦的自定义方式来实现验证逻辑。

编辑:这是一种自定义的痛苦方式:)

String lastValidatedNumber;
String lastRejectedNumber;

// this will be called upon user interaction or re-initiation as commented below
String validatePhone(String number) {
  if (lastValidatedNumber == number) {
    return null;
  } else if (lastRejectedNumber == number) {
    return "Phone number is invalid";
  } else {
    initiateAsyncPhoneValidation(number);
    return "Validation in progress";
  }
}

Future<void> initiateAsyncPhoneValidation(String number) async {
  final val = await Requests.get(
          "http://apilayer.net/api/validate?value=$number",
          json: true);

  if (val['valid']) {
    lastValidatedNumber = number;
  } else {
    lastRejectedNumber = number;
  }
  _formKey.currentState.validate(); // this will re-initiate the validation
}

您需要一个表单密钥:

final _formKey = GlobalKey<FormState>();

您的表单应自动验证:

    child: Form(
      key: _formKey,
      autovalidate: true,
      child: TextFormField(
        validator: validatePhone
      )
    )

我不确定这是否行得通,但值得一试。