我在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
中设置的初始值。知道如何使这项工作有效吗?
答案 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,
),
...
。
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,
),
),
);
}
}
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
应该return是String
,而不是'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
)
)
我不确定这是否行得通,但值得一试。