在验证器之外更新 TextFormField 的错误
Posted
技术标签:
【中文标题】在验证器之外更新 TextFormField 的错误【英文标题】:Update TextFormField's error outside of the validator 【发布时间】:2020-03-25 02:18:34 【问题描述】:根据 TextFormField 文档,在 TextFormField 下方显示错误的唯一方法是从验证器函数返回错误字符串。但是,我有一个文本输入,只能在调用服务器后进行验证,并且服务器的响应(如果有效)也需要稍后使用。因此,我只在用户按下提交时才这样做。但是,如果服务器返回无效响应,我需要更新错误文本,但由于我在验证器之外,所以我不能这样做。
我接近这个了吗?有没有办法做到这一点?
TextFormField(
autofocus: true,
onSaved: (String value) => passcode = value,
),
SizedBox(50.0),
RaisedButtton(
child: Text('SUBMIT'),
onPressed: () async
_formKey.currentState.save();
dynamic response = await someServerCall();
if (response.token)
// Valid, use token
else
// INVALID, update error text somehow
)
(这里的每样东西都有不同的父母,包括Column和Form,但这基本上是我正在做的)
【问题讨论】:
【参考方案1】:您可以使用flutter_form_bloc。
每个字段都有addError
方法,你可以在任何地方调用,在你的情况下,它会在收到服务器的响应后在onSubmitting
方法中。
class MyFormBloc extends FormBloc<String, String>
final email = TextFieldBloc();
MyFormBloc()
addFieldBlocs(fieldBlocs: [email]);
@override
void onSubmitting() async
// Awesome logic...
username.addError('That email is taken. Try another.');
您还可以使用具有去抖动时间的异步验证器
class MyFormBloc extends FormBloc<String, String>
final username = TextFieldBloc(
asyncValidatorDebounceTime: Duration(milliseconds: 300),
);
MyFormBloc()
addFieldBlocs(fieldBlocs: [username]);
username.addAsyncValidators([_checkUsername]);
Future<String> _checkUsername(String username) async
await Future.delayed(Duration(milliseconds: 500));
if (username.toLowerCase() != 'flutter dev')
return 'That username is already taken';
return null;
这是一个可以运行的小演示,教程在form bloc website
pubspec.yaml
dependencies:
flutter_form_bloc: ^0.11.0
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
void main() => runApp(App());
class App extends StatelessWidget
const App(Key key) : super(key: key);
@override
Widget build(BuildContext context)
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SubmissionErrorToFieldForm(),
);
class SubmissionErrorToFieldFormBloc extends FormBloc<String, String>
final username = TextFieldBloc();
SubmissionErrorToFieldFormBloc()
addFieldBlocs(
fieldBlocs: [
username,
],
);
@override
void onSubmitting() async
print(username.value);
await Future<void>.delayed(Duration(milliseconds: 500));
if (username.value.toLowerCase() == 'dev')
username.addError(
'Cached - That username is taken. Try another.',
isPermanent: true,
);
emitFailure(failureResponse: 'Cached error was added to username field.');
else
username.addError('That username is taken. Try another.');
emitFailure(failureResponse: 'Error was added to username field.');
class SubmissionErrorToFieldForm extends StatelessWidget
@override
Widget build(BuildContext context)
return BlocProvider(
create: (context) => SubmissionErrorToFieldFormBloc(),
child: Builder(
builder: (context)
final formBloc =
BlocProvider.of<SubmissionErrorToFieldFormBloc>(context);
return Theme(
data: Theme.of(context).copyWith(
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
),
),
),
child: Scaffold(
appBar: AppBar(title: Text('Submission Error to Field')),
body: FormBlocListener<SubmissionErrorToFieldFormBloc, String,
String>(
onSubmitting: (context, state)
LoadingDialog.show(context);
,
onSuccess: (context, state)
LoadingDialog.hide(context);
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => SuccessScreen()));
,
onFailure: (context, state)
LoadingDialog.hide(context);
Scaffold.of(context).showSnackBar(
SnackBar(content: Text(state.failureResponse)));
,
child: SingleChildScrollView(
physics: ClampingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
TextFieldBlocBuilder(
textFieldBloc: formBloc.username,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
labelText: 'Username',
prefixIcon: Icon(Icons.sentiment_very_satisfied),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text('"dev" will add a cached error'),
),
RaisedButton(
onPressed: formBloc.submit,
child: Text('SUBMIT'),
),
],
),
),
),
),
),
);
,
),
);
class LoadingDialog extends StatelessWidget
static void show(BuildContext context, Key key) => showDialog<void>(
context: context,
useRootNavigator: false,
barrierDismissible: false,
builder: (_) => LoadingDialog(key: key),
).then((_) => FocusScope.of(context).requestFocus(FocusNode()));
static void hide(BuildContext context) => Navigator.pop(context);
LoadingDialog(Key key) : super(key: key);
@override
Widget build(BuildContext context)
return WillPopScope(
onWillPop: () async => false,
child: Center(
child: Card(
child: Container(
width: 80,
height: 80,
padding: EdgeInsets.all(12.0),
child: CircularProgressIndicator(),
),
),
),
);
class SuccessScreen extends StatelessWidget
SuccessScreen(Key key) : super(key: key);
@override
Widget build(BuildContext context)
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.tag_faces, size: 100),
SizedBox(height: 10),
Text(
'Success',
style: TextStyle(fontSize: 54, color: Colors.black),
textAlign: TextAlign.center,
),
SizedBox(height: 10),
RaisedButton.icon(
onPressed: () => Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => SubmissionErrorToFieldForm())),
icon: Icon(Icons.replay),
label: Text('AGAIN'),
),
],
),
),
);
【讨论】:
【参考方案2】:添加一个布尔检查,您将根据服务器调用进行更改
TextFormField(
validator: (value)
if (hasErrorAfterServerCall)
return 'Your Error Message';
else
return null;
,
);
服务器调用完成后,您可以再次验证表单
_formKey.currentState!.validate();
【讨论】:
【参考方案3】:为什么不在验证器函数中添加服务器调用。 在 TextFormField 中使用验证器,如下所示,
TextFormField(
validator: _validateEmail,
onSaved: (String value)
email = value;
,
),
String _validateEmail(String value) async
//call to a server inside a validator function
dynamic response = await someServerCall();
String _token="";
if (response.token)
// Valid, use token
setState(()
_token = response.token
);
return null;
else
// INVALID, update error text somehow
return "error";
如果验证器为空,那么它不会显示任何错误,但如果它得到任何字符串,那么它将将该字符串显示为错误。 现在是按钮
RaisedButtton(
child: Text('SUBMIT'),
onPressed: ()
if (_formKey.currentState.validate()) _formKey.currentState.save();
)
【讨论】:
之后我将如何使用令牌?_validateEmail
在这里进行(昂贵的)服务器调用,但无法将令牌恢复到可用的上下文中。
令牌如果经过验证可以存储。我已经编辑了答案。
另外,验证器不能是异步的。如果将自己标记为异步,则返回 Future<String>
,不能只返回 String
。
好的,有一个解决方案,但有点棘手。您可以直接点击服务器并等待响应,然后调用验证器。您也可以应用预检查来检查密码是否太短、电子邮件是否包含@xxx.xx 等。在调用查询之前。如果这些错误仍然存在,则直接调用验证器并通过它抛出错误。以上是关于在验证器之外更新 TextFormField 的错误的主要内容,如果未能解决你的问题,请参考以下文章
TextFormField 验证器参数类型“动态函数()”不能分配给
AlertDialog 中的 TextFormField 未验证
如何使用 GetX 实现 Flutter TextFormField 验证器?