如何使用 Form 和 GlobalKey 验证 alertDialog 上的文本输入?
How to validate text input on alertDialog using Form and GlobalKey?
我在 alertDialog
上有一个 textfield
接受 Email
并想验证它。点击 forgot password
按钮后,alertDialog 在当前登录屏幕前打开。
我已经实现了登录验证,并试图使用类似的逻辑来实现上述目标。对于登录验证,我使用了 GlobalKey
(_formKey) 和 Form
小部件,效果很好。我正在使用另一个名为 _resetKey
的 GlobalKey
来获取 currentState
验证,然后保存它的状态。尽管这种方法有效,但我看到验证消息也显示在 Email
和 Password
字段上。即,如果我点击 'forgot password' 打开对话框,然后点击 send email
,它会正确显示验证消息,但同时,在点击取消后也会触发登录屏幕的验证消息警报对话框中的按钮。像这样:
对于 alertDialog 验证,下面是我的代码:
// Creates an alertDialog for the user to enter their email
Future<String> _resetDialogBox() {
final resetEmailController = TextEditingController();
return showDialog<String>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: new Text('Reset Password'),
content: new SingleChildScrollView(
child: new Form(
key: _resetKey,
autovalidate: _validate,
child: ListBody(
children: <Widget>[
new Text(
'Enter the Email Address associated with your account.',
style: TextStyle(fontSize: 14.0),),
Padding(
padding: EdgeInsets.all(10.0),
),
Row(
children: <Widget>[
new Padding(
padding: EdgeInsets.only(top: 8.0),
child: Icon(
Icons.email, size: 20.0,
),
),
new Expanded(
child: TextFormField(
validator: validateEmail,
onSaved: (String val) {
resetEmail = val;
},
new FlatButton(
child: new Text(
'SEND EMAIL', style: TextStyle(color: Colors.black),),
onPressed: () {
setState(() {
_sendResetEmail();
});
void _sendResetEmail() {
final resetEmailController = TextEditingController();
resetEmail = resetEmailController.text;
if (_resetKey.currentState.validate()) {
_resetKey.currentState.save();
try {
Fluttertoast.showToast(
msg: "Sending password-reset email to: $resetEmail",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 4);
_auth.sendPasswordResetEmail(email: resetEmail);
} catch (exception) {
print(exception);
Fluttertoast.showToast(
msg: "${exception.toString()}",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 4);
}
}
else {
setState(() {
_validate = true;
});
}
}
使用_formKey
gist 的登录验证如下:
// Creates the email and password text fields
Widget _textFields() {
return Form(
key: _formKey,
autovalidate: _validate,
child: Column(
children: <Widget>[
Container(
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(width: 0.5, color: Colors.grey),
),
),
margin: const EdgeInsets.symmetric(
vertical: 25.0, horizontal: 65.0),
// Email text field
child: Row(
children: <Widget>[
new Padding(
padding: EdgeInsets.symmetric(
vertical: 10.0, horizontal: 15.0),
child: Icon(
Icons.email,
color: Colors.white,
),
),
new Expanded(
child: TextFormField(
validator: validateEmail,
onSaved: (String val) {
email = val;
},
我认为这与 2 个键有关,因为 alertDialog 显示在当前 activity 的前面。我怎样才能用 _formKey
实现或者有没有其他方法?
************ 需要完整代码 ************
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: LoginScreen(),
),
);
}
}
class LoginScreen extends StatefulWidget {
@override
LoginScreenState createState() => new LoginScreenState();
}
class LoginScreenState extends State<LoginScreen> {
final FirebaseAuth _auth = FirebaseAuth.instance;
final _formKey = GlobalKey<FormState>();
final _resetKey = GlobalKey<FormState>();
bool _validate = false;
String email;
String password;
String resetEmail;
// The controller for the email field
final _emailController = TextEditingController();
// The controller for the password field
final _passwordController = TextEditingController();
// Creates the 'forgot password' and 'create account' buttons
Widget _accountButtons() {
return Container(
child: Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
child: new FlatButton(
padding: const EdgeInsets.only(
top: 50.0, right: 150.0),
onPressed: () => sendPasswordResetEmail(),
child: Text("Forgot Password",
style: TextStyle(color: Colors.white)),
),
)
]),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Container(
child: new FlatButton(
padding: const EdgeInsets.only(top: 50.0),
onPressed: () =>
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CreateAccountPage())),
child: Text(
"Register",
style: TextStyle(color: Colors.white),
),
),
)
],
)
])),
);
}
// Creates the email and password text fields
Widget _textFields() {
return Form(
key: _formKey,
autovalidate: _validate,
child: Column(
children: <Widget>[
Container(
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(width: 0.5, color: Colors.grey),
),
),
margin: const EdgeInsets.symmetric(
vertical: 25.0, horizontal: 65.0),
// Email text field
child: Row(
children: <Widget>[
new Padding(
padding: EdgeInsets.symmetric(
vertical: 10.0, horizontal: 15.0),
child: Icon(
Icons.email,
color: Colors.white,
),
),
new Expanded(
child: TextFormField(
validator: validateEmail,
onSaved: (String val) {
email = val;
},
keyboardType: TextInputType.emailAddress,
autofocus: true,
// cursorColor: Colors.green,
controller: _emailController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Email',
// contentPadding: EdgeInsets.fromLTRB(45.0, 10.0, 20.0, 1.0),
contentPadding: EdgeInsets.only(left: 55.0, top: 15.0),
hintStyle: TextStyle(color: Colors.white),
),
style: TextStyle(color: Colors.white),
),
)
],
),
),
// Password text field
Container(
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(
width: 0.5,
color: Colors.grey,
),
),
),
margin: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 65.0),
child: Row(
children: <Widget>[
new Padding(
padding: EdgeInsets.symmetric(
vertical: 10.0, horizontal: 15.0),
child: Icon(
Icons.lock,
color: Colors.white,
),
),
new Expanded(
child: TextFormField(
validator: _validatePassword,
onSaved: (String val) {
password = val;
},
// cursorColor: Colors.green,
controller: _passwordController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Password',
contentPadding: EdgeInsets.only(
left: 50.0, top: 15.0),
hintStyle: TextStyle(color: Colors.white),
),
style: TextStyle(color: Colors.white),
// Make the characters in this field hidden
obscureText: true),
)
],
),
)
],
)
);
}
// Creates the button to sign in
Widget _signInButton() {
return new Container(
width: 200.0,
margin: const EdgeInsets.only(top: 20.0),
padding: const EdgeInsets.only(left: 20.0, right: 20.0),
child: new Row(
children: <Widget>[
new Expanded(
child: RaisedButton(
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(30.0)),
splashColor: Colors.white,
color: Colors.green,
child: new Row(
children: <Widget>[
new Padding(
padding: const EdgeInsets.only(left: 35.0),
child: Text(
"Sign in",
style: TextStyle(color: Colors.white, fontSize: 18.0),
textAlign: TextAlign.center,
),
),
],
),
onPressed: () {
setState(() {
_signIn();
});
}),
),
],
));
}
// Signs in the user
void _signIn() async {
// Grab the text from the text fields
final email = _emailController.text;
final password = _passwordController.text;
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
try {
Fluttertoast.showToast(
msg: "Signing in...",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 2);
firebaseUser = await _auth.signInWithEmailAndPassword(
email: email, password: password);
// If user successfully signs in, go to the pro categories page
Navigator.pushReplacement(context,
MaterialPageRoute(
builder: (context) => ProCategories(firebaseUser)));
} catch (exception) {
print(exception.toString());
Fluttertoast.showToast(
msg: "${exception.toString()}",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 3);
}
}
else {
setState(() {
_validate = true;
});
}
}
// Creates an alertDialog for the user to enter their email
Future<String> _resetDialogBox() {
final resetEmailController = TextEditingController();
return showDialog<String>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: new Text('Reset Password'),
content: new SingleChildScrollView(
child: new Form(
key: _resetKey,
autovalidate: _validate,
child: ListBody(
children: <Widget>[
new Text(
'Enter the Email Address associated with your account.',
style: TextStyle(fontSize: 14.0),),
Padding(
padding: EdgeInsets.all(10.0),
),
Row(
children: <Widget>[
new Padding(
padding: EdgeInsets.only(top: 8.0),
child: Icon(
Icons.email, size: 20.0,
),
),
new Expanded(
child: TextFormField(
validator: validateEmail,
onSaved: (String val) {
resetEmail = val;
},
keyboardType: TextInputType.emailAddress,
autofocus: true,
decoration: new InputDecoration(
border: InputBorder.none,
hintText: 'Email',
contentPadding: EdgeInsets.only(
left: 70.0, top: 15.0),
hintStyle: TextStyle(
color: Colors.black, fontSize: 14.0)
),
style: TextStyle(color: Colors.black),
),
)
],
),
new Column(
children: <Widget>[
Container(
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(
width: 0.5, color: Colors.black)
)
),
)
]
),
],
),
)
),
actions: <Widget>[
new FlatButton(
child: new Text('CANCEL', style: TextStyle(color: Colors.black),),
onPressed: () {
Navigator.of(context).pop("");
},
),
new FlatButton(
child: new Text(
'SEND EMAIL', style: TextStyle(color: Colors.black),),
onPressed: () {
setState(() {
_sendResetEmail();
});
Navigator.of(context).pop(resetEmail);
},
),
],
);
},
);
}
// Sends a password-reset link to the given email address
void sendPasswordResetEmail() async {
String resetEmail = await _resetDialogBox();
// When this is true, the user pressed 'cancel', so do nothing
if (resetEmail == "") {
return;
}
try {
Fluttertoast.showToast(
msg: "Sending password-reset email to: $resetEmail",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 4);
_auth.sendPasswordResetEmail(email: resetEmail);
} catch (exception) {
print(exception);
Fluttertoast.showToast(
msg: "${exception.toString()}",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 4);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
// prevent pixel overflow when typing
resizeToAvoidBottomPadding: false,
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
"",
),
fit: BoxFit.cover)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
// QuickCarl logo at the top
Image(
alignment: Alignment.bottomCenter,
image: AssetImage(""),
width: 180.0,
height: 250.0,
),
new Text('',
style: TextStyle(
fontStyle: FontStyle.italic,
fontSize: 12.0,
color: Colors.white)
),
_textFields(),
_signInButton(),
_accountButtons()
],
),
),
);
}
String validateEmail(String value) {
String pattern = r'^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regExp = new RegExp(pattern);
if (value.length == 0) {
return "Email is required";
} else if (!regExp.hasMatch(value)) {
return "Invalid Email";
} else {
return null;
}
}
String _validatePassword(String value) {
if (value.length == 0) {
return 'Password is required';
}
if (value.length < 4) {
return 'Incorrect password';
}
}
void _sendResetEmail() {
final resetEmailController = TextEditingController();
resetEmail = resetEmailController.text;
if (_resetKey.currentState.validate()) {
_resetKey.currentState.save();
try {
Fluttertoast.showToast(
msg: "Sending password-reset email to: $resetEmail",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 4);
_auth.sendPasswordResetEmail(email: resetEmail);
} catch (exception) {
print(exception);
Fluttertoast.showToast(
msg: "${exception.toString()}",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 4);
}
}
else {
setState(() {
_validate = true;
});
}
}
}
嗯,主要有两个问题:
第一个是您需要使用对话框本地的另一个 'validate' 变量。否则,当您将其设置为 true 并调用 setState()
时,将重建整个页面并根据 validate
值检查所有字段。
但即使您这样做,对话框中的 validate
也不会产生任何结果,因为当您调用 setState()
时,不会重新创建 Form
小部件validate
的更改值不会作为参数注入。
要了解这个问题,请转到我前段时间写的this article in Medium。
解决这两个问题的方法,根据文章中的解释,是创建一个全新的stateful widget。因此,当调用 setState()
时,会重建 Form
并考虑 validate
的新值。
这是让它工作的代码:
// Creates an alertDialog for the user to enter their email
Future<String> _resetDialogBox() {
return showDialog<String>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return CustomAlertDialog(
title: "Reset email",
auth: _auth,
);
},
);
}
class CustomAlertDialog extends StatefulWidget {
final String title;
final FirebaseAuth auth;
const CustomAlertDialog({Key key, this.title, this.auth})
: super(key: key);
@override
CustomAlertDialogState createState() {
return new CustomAlertDialogState();
}
}
class CustomAlertDialogState extends State<CustomAlertDialog> {
final _resetKey = GlobalKey<FormState>();
final _resetEmailController = TextEditingController();
String _resetEmail;
bool _resetValidate = false;
StreamController<bool> rebuild = StreamController<bool>();
bool _sendResetEmail() {
_resetEmail = _resetEmailController.text;
if (_resetKey.currentState.validate()) {
_resetKey.currentState.save();
try {
// You could consider using async/await here
widget.auth.sendPasswordResetEmail(email: _resetEmail);
return true;
} catch (exception) {
print(exception);
}
} else {
setState(() {
_resetValidate = true;
});
return false;
}
}
@override
Widget build(BuildContext context) {
return Container(
child: AlertDialog(
title: new Text(widget.title),
content: new SingleChildScrollView(
child: Form(
key: _resetKey,
autovalidate: _resetValidate,
child: ListBody(
children: <Widget>[
new Text(
'Enter the Email Address associated with your account.',
style: TextStyle(fontSize: 14.0),
),
Padding(
padding: EdgeInsets.all(10.0),
),
Row(
children: <Widget>[
new Padding(
padding: EdgeInsets.only(top: 8.0),
child: Icon(
Icons.email,
size: 20.0,
),
),
new Expanded(
child: TextFormField(
validator: validateEmail,
onSaved: (String val) {
_resetEmail = val;
},
controller: _resetEmailController,
keyboardType: TextInputType.emailAddress,
autofocus: true,
decoration: new InputDecoration(
border: InputBorder.none,
hintText: 'Email',
contentPadding:
EdgeInsets.only(left: 70.0, top: 15.0),
hintStyle:
TextStyle(color: Colors.black, fontSize: 14.0)),
style: TextStyle(color: Colors.black),
),
)
],
),
new Column(children: <Widget>[
Container(
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(
width: 0.5, color: Colors.black))),
)
]),
],
),
),
),
actions: <Widget>[
new FlatButton(
child: new Text(
'CANCEL',
style: TextStyle(color: Colors.black),
),
onPressed: () {
Navigator.of(context).pop("");
},
),
new FlatButton(
child: new Text(
'SEND EMAIL',
style: TextStyle(color: Colors.black),
),
onPressed: () {
if (_sendResetEmail()) {
Navigator.of(context).pop(_resetEmail);
}
},
),
],
),
);
}
}
String validateEmail(String value) {
String pattern =
r'^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regExp = new RegExp(pattern);
if (value.length == 0) {
return "Email is required";
} else if (!regExp.hasMatch(value)) {
return "Invalid Email";
} else {
return null;
}
}
我不得不提取 validateEmail()
方法以使其可用于新的小部件。
我知道这个post来不及了,但我想分享我的代码,以便同汤中的任何人都能从中得到帮助。该表单也使用正则表达式过滤器进行实时验证。
以下代码片段改编自 chemamolins and from this blog 提供的解决方案。
final TextEditingController _nameController = TextEditingController();
final TextEditingController _phoneController = TextEditingController();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final GlobalKey<FormFieldState> _nameFormKey = GlobalKey<FormFieldState>();
final GlobalKey<FormFieldState> _phoneFormKey = GlobalKey<FormFieldState>();
bool _isFormValid() {
return ((_nameFormKey.currentState.isValid &&
_phoneFormKey.currentState.isValid));
}
void _submit() {
print('Name: ' +
_nameController.text +
', problem: ' +
_phoneController.text);
}
Future<void> _registerDialogBox(BuildContext context) async {
return await showDialog<String>(
context: context,
barrierDismissible: false,
builder: (context) {
bool _isSubmitButtonEnabled = false;
return StatefulBuilder(builder: (context, setState) {
return AlertDialog(
scrollable: true,
title: Text('Register'),
content: Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
new TextFormField(
key: _nameFormKey,
controller: _nameController,
maxLength: 30,
maxLengthEnforced: true,
keyboardType: TextInputType.name,
inputFormatters: [new FilteringTextInputFormatter.allow(RegExp("[a-zA-Z]")), ],
decoration: InputDecoration(
labelText: 'Name',
icon: Icon(Icons.account_box),
),
onChanged: (value) {
setState(() {
_isSubmitButtonEnabled = _isFormValid();
_nameFormKey.currentState.validate();
});
},
validator: (value) {
if (value.length < 3 )
return 'Min 3 and Max 30 characters';
else
return null;
}),
new TextFormField(
key: _phoneFormKey,
maxLength: 13,
maxLengthEnforced: true,
controller: _phoneController,
keyboardType: TextInputType.phone,
inputFormatters: [new FilteringTextInputFormatter.allow(RegExp("[0-9+]"))],
decoration: InputDecoration(
labelText: 'Phone',
icon: Icon(Icons.phone),
),
onChanged: (value) {
setState(() {
_isSubmitButtonEnabled = _isFormValid();
_phoneFormKey.currentState.validate();
});
},
validator: (value) {
if (value.length > 1 && value.length < 10 )
return 'Minimum 10 digits';
else
return null;
}),
],
),
),
),
actions: [
RaisedButton(
child: Text("Submit"),
onPressed: _isSubmitButtonEnabled ? () => _submit() : null)
],
);
});
});
}
现在在 Widget build
中,可以这样调用 Future
函数:
Padding(
padding: EdgeInsets.only(top: 5.0, bottom: 20.0),
child: RaisedButton(onPressed: ()async {
await _registerDialogBox(context);
}),
),
我在 alertDialog
上有一个 textfield
接受 Email
并想验证它。点击 forgot password
按钮后,alertDialog 在当前登录屏幕前打开。
我已经实现了登录验证,并试图使用类似的逻辑来实现上述目标。对于登录验证,我使用了 GlobalKey
(_formKey) 和 Form
小部件,效果很好。我正在使用另一个名为 _resetKey
的 GlobalKey
来获取 currentState
验证,然后保存它的状态。尽管这种方法有效,但我看到验证消息也显示在 Email
和 Password
字段上。即,如果我点击 'forgot password' 打开对话框,然后点击 send email
,它会正确显示验证消息,但同时,在点击取消后也会触发登录屏幕的验证消息警报对话框中的按钮。像这样:
对于 alertDialog 验证,下面是我的代码:
// Creates an alertDialog for the user to enter their email
Future<String> _resetDialogBox() {
final resetEmailController = TextEditingController();
return showDialog<String>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: new Text('Reset Password'),
content: new SingleChildScrollView(
child: new Form(
key: _resetKey,
autovalidate: _validate,
child: ListBody(
children: <Widget>[
new Text(
'Enter the Email Address associated with your account.',
style: TextStyle(fontSize: 14.0),),
Padding(
padding: EdgeInsets.all(10.0),
),
Row(
children: <Widget>[
new Padding(
padding: EdgeInsets.only(top: 8.0),
child: Icon(
Icons.email, size: 20.0,
),
),
new Expanded(
child: TextFormField(
validator: validateEmail,
onSaved: (String val) {
resetEmail = val;
},
new FlatButton(
child: new Text(
'SEND EMAIL', style: TextStyle(color: Colors.black),),
onPressed: () {
setState(() {
_sendResetEmail();
});
void _sendResetEmail() {
final resetEmailController = TextEditingController();
resetEmail = resetEmailController.text;
if (_resetKey.currentState.validate()) {
_resetKey.currentState.save();
try {
Fluttertoast.showToast(
msg: "Sending password-reset email to: $resetEmail",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 4);
_auth.sendPasswordResetEmail(email: resetEmail);
} catch (exception) {
print(exception);
Fluttertoast.showToast(
msg: "${exception.toString()}",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 4);
}
}
else {
setState(() {
_validate = true;
});
}
}
使用_formKey
gist 的登录验证如下:
// Creates the email and password text fields
Widget _textFields() {
return Form(
key: _formKey,
autovalidate: _validate,
child: Column(
children: <Widget>[
Container(
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(width: 0.5, color: Colors.grey),
),
),
margin: const EdgeInsets.symmetric(
vertical: 25.0, horizontal: 65.0),
// Email text field
child: Row(
children: <Widget>[
new Padding(
padding: EdgeInsets.symmetric(
vertical: 10.0, horizontal: 15.0),
child: Icon(
Icons.email,
color: Colors.white,
),
),
new Expanded(
child: TextFormField(
validator: validateEmail,
onSaved: (String val) {
email = val;
},
我认为这与 2 个键有关,因为 alertDialog 显示在当前 activity 的前面。我怎样才能用 _formKey
实现或者有没有其他方法?
************ 需要完整代码 ************
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: LoginScreen(),
),
);
}
}
class LoginScreen extends StatefulWidget {
@override
LoginScreenState createState() => new LoginScreenState();
}
class LoginScreenState extends State<LoginScreen> {
final FirebaseAuth _auth = FirebaseAuth.instance;
final _formKey = GlobalKey<FormState>();
final _resetKey = GlobalKey<FormState>();
bool _validate = false;
String email;
String password;
String resetEmail;
// The controller for the email field
final _emailController = TextEditingController();
// The controller for the password field
final _passwordController = TextEditingController();
// Creates the 'forgot password' and 'create account' buttons
Widget _accountButtons() {
return Container(
child: Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
child: new FlatButton(
padding: const EdgeInsets.only(
top: 50.0, right: 150.0),
onPressed: () => sendPasswordResetEmail(),
child: Text("Forgot Password",
style: TextStyle(color: Colors.white)),
),
)
]),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Container(
child: new FlatButton(
padding: const EdgeInsets.only(top: 50.0),
onPressed: () =>
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CreateAccountPage())),
child: Text(
"Register",
style: TextStyle(color: Colors.white),
),
),
)
],
)
])),
);
}
// Creates the email and password text fields
Widget _textFields() {
return Form(
key: _formKey,
autovalidate: _validate,
child: Column(
children: <Widget>[
Container(
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(width: 0.5, color: Colors.grey),
),
),
margin: const EdgeInsets.symmetric(
vertical: 25.0, horizontal: 65.0),
// Email text field
child: Row(
children: <Widget>[
new Padding(
padding: EdgeInsets.symmetric(
vertical: 10.0, horizontal: 15.0),
child: Icon(
Icons.email,
color: Colors.white,
),
),
new Expanded(
child: TextFormField(
validator: validateEmail,
onSaved: (String val) {
email = val;
},
keyboardType: TextInputType.emailAddress,
autofocus: true,
// cursorColor: Colors.green,
controller: _emailController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Email',
// contentPadding: EdgeInsets.fromLTRB(45.0, 10.0, 20.0, 1.0),
contentPadding: EdgeInsets.only(left: 55.0, top: 15.0),
hintStyle: TextStyle(color: Colors.white),
),
style: TextStyle(color: Colors.white),
),
)
],
),
),
// Password text field
Container(
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(
width: 0.5,
color: Colors.grey,
),
),
),
margin: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 65.0),
child: Row(
children: <Widget>[
new Padding(
padding: EdgeInsets.symmetric(
vertical: 10.0, horizontal: 15.0),
child: Icon(
Icons.lock,
color: Colors.white,
),
),
new Expanded(
child: TextFormField(
validator: _validatePassword,
onSaved: (String val) {
password = val;
},
// cursorColor: Colors.green,
controller: _passwordController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Password',
contentPadding: EdgeInsets.only(
left: 50.0, top: 15.0),
hintStyle: TextStyle(color: Colors.white),
),
style: TextStyle(color: Colors.white),
// Make the characters in this field hidden
obscureText: true),
)
],
),
)
],
)
);
}
// Creates the button to sign in
Widget _signInButton() {
return new Container(
width: 200.0,
margin: const EdgeInsets.only(top: 20.0),
padding: const EdgeInsets.only(left: 20.0, right: 20.0),
child: new Row(
children: <Widget>[
new Expanded(
child: RaisedButton(
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(30.0)),
splashColor: Colors.white,
color: Colors.green,
child: new Row(
children: <Widget>[
new Padding(
padding: const EdgeInsets.only(left: 35.0),
child: Text(
"Sign in",
style: TextStyle(color: Colors.white, fontSize: 18.0),
textAlign: TextAlign.center,
),
),
],
),
onPressed: () {
setState(() {
_signIn();
});
}),
),
],
));
}
// Signs in the user
void _signIn() async {
// Grab the text from the text fields
final email = _emailController.text;
final password = _passwordController.text;
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
try {
Fluttertoast.showToast(
msg: "Signing in...",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 2);
firebaseUser = await _auth.signInWithEmailAndPassword(
email: email, password: password);
// If user successfully signs in, go to the pro categories page
Navigator.pushReplacement(context,
MaterialPageRoute(
builder: (context) => ProCategories(firebaseUser)));
} catch (exception) {
print(exception.toString());
Fluttertoast.showToast(
msg: "${exception.toString()}",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 3);
}
}
else {
setState(() {
_validate = true;
});
}
}
// Creates an alertDialog for the user to enter their email
Future<String> _resetDialogBox() {
final resetEmailController = TextEditingController();
return showDialog<String>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: new Text('Reset Password'),
content: new SingleChildScrollView(
child: new Form(
key: _resetKey,
autovalidate: _validate,
child: ListBody(
children: <Widget>[
new Text(
'Enter the Email Address associated with your account.',
style: TextStyle(fontSize: 14.0),),
Padding(
padding: EdgeInsets.all(10.0),
),
Row(
children: <Widget>[
new Padding(
padding: EdgeInsets.only(top: 8.0),
child: Icon(
Icons.email, size: 20.0,
),
),
new Expanded(
child: TextFormField(
validator: validateEmail,
onSaved: (String val) {
resetEmail = val;
},
keyboardType: TextInputType.emailAddress,
autofocus: true,
decoration: new InputDecoration(
border: InputBorder.none,
hintText: 'Email',
contentPadding: EdgeInsets.only(
left: 70.0, top: 15.0),
hintStyle: TextStyle(
color: Colors.black, fontSize: 14.0)
),
style: TextStyle(color: Colors.black),
),
)
],
),
new Column(
children: <Widget>[
Container(
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(
width: 0.5, color: Colors.black)
)
),
)
]
),
],
),
)
),
actions: <Widget>[
new FlatButton(
child: new Text('CANCEL', style: TextStyle(color: Colors.black),),
onPressed: () {
Navigator.of(context).pop("");
},
),
new FlatButton(
child: new Text(
'SEND EMAIL', style: TextStyle(color: Colors.black),),
onPressed: () {
setState(() {
_sendResetEmail();
});
Navigator.of(context).pop(resetEmail);
},
),
],
);
},
);
}
// Sends a password-reset link to the given email address
void sendPasswordResetEmail() async {
String resetEmail = await _resetDialogBox();
// When this is true, the user pressed 'cancel', so do nothing
if (resetEmail == "") {
return;
}
try {
Fluttertoast.showToast(
msg: "Sending password-reset email to: $resetEmail",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 4);
_auth.sendPasswordResetEmail(email: resetEmail);
} catch (exception) {
print(exception);
Fluttertoast.showToast(
msg: "${exception.toString()}",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 4);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
// prevent pixel overflow when typing
resizeToAvoidBottomPadding: false,
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(
"",
),
fit: BoxFit.cover)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
// QuickCarl logo at the top
Image(
alignment: Alignment.bottomCenter,
image: AssetImage(""),
width: 180.0,
height: 250.0,
),
new Text('',
style: TextStyle(
fontStyle: FontStyle.italic,
fontSize: 12.0,
color: Colors.white)
),
_textFields(),
_signInButton(),
_accountButtons()
],
),
),
);
}
String validateEmail(String value) {
String pattern = r'^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regExp = new RegExp(pattern);
if (value.length == 0) {
return "Email is required";
} else if (!regExp.hasMatch(value)) {
return "Invalid Email";
} else {
return null;
}
}
String _validatePassword(String value) {
if (value.length == 0) {
return 'Password is required';
}
if (value.length < 4) {
return 'Incorrect password';
}
}
void _sendResetEmail() {
final resetEmailController = TextEditingController();
resetEmail = resetEmailController.text;
if (_resetKey.currentState.validate()) {
_resetKey.currentState.save();
try {
Fluttertoast.showToast(
msg: "Sending password-reset email to: $resetEmail",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 4);
_auth.sendPasswordResetEmail(email: resetEmail);
} catch (exception) {
print(exception);
Fluttertoast.showToast(
msg: "${exception.toString()}",
toastLength: Toast.LENGTH_LONG,
bgcolor: "#e74c3c",
textcolor: '#ffffff',
timeInSecForIos: 4);
}
}
else {
setState(() {
_validate = true;
});
}
}
}
嗯,主要有两个问题:
第一个是您需要使用对话框本地的另一个 'validate' 变量。否则,当您将其设置为 true 并调用
setState()
时,将重建整个页面并根据validate
值检查所有字段。但即使您这样做,对话框中的
validate
也不会产生任何结果,因为当您调用setState()
时,不会重新创建Form
小部件validate
的更改值不会作为参数注入。
要了解这个问题,请转到我前段时间写的this article in Medium。
解决这两个问题的方法,根据文章中的解释,是创建一个全新的stateful widget。因此,当调用 setState()
时,会重建 Form
并考虑 validate
的新值。
这是让它工作的代码:
// Creates an alertDialog for the user to enter their email
Future<String> _resetDialogBox() {
return showDialog<String>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return CustomAlertDialog(
title: "Reset email",
auth: _auth,
);
},
);
}
class CustomAlertDialog extends StatefulWidget {
final String title;
final FirebaseAuth auth;
const CustomAlertDialog({Key key, this.title, this.auth})
: super(key: key);
@override
CustomAlertDialogState createState() {
return new CustomAlertDialogState();
}
}
class CustomAlertDialogState extends State<CustomAlertDialog> {
final _resetKey = GlobalKey<FormState>();
final _resetEmailController = TextEditingController();
String _resetEmail;
bool _resetValidate = false;
StreamController<bool> rebuild = StreamController<bool>();
bool _sendResetEmail() {
_resetEmail = _resetEmailController.text;
if (_resetKey.currentState.validate()) {
_resetKey.currentState.save();
try {
// You could consider using async/await here
widget.auth.sendPasswordResetEmail(email: _resetEmail);
return true;
} catch (exception) {
print(exception);
}
} else {
setState(() {
_resetValidate = true;
});
return false;
}
}
@override
Widget build(BuildContext context) {
return Container(
child: AlertDialog(
title: new Text(widget.title),
content: new SingleChildScrollView(
child: Form(
key: _resetKey,
autovalidate: _resetValidate,
child: ListBody(
children: <Widget>[
new Text(
'Enter the Email Address associated with your account.',
style: TextStyle(fontSize: 14.0),
),
Padding(
padding: EdgeInsets.all(10.0),
),
Row(
children: <Widget>[
new Padding(
padding: EdgeInsets.only(top: 8.0),
child: Icon(
Icons.email,
size: 20.0,
),
),
new Expanded(
child: TextFormField(
validator: validateEmail,
onSaved: (String val) {
_resetEmail = val;
},
controller: _resetEmailController,
keyboardType: TextInputType.emailAddress,
autofocus: true,
decoration: new InputDecoration(
border: InputBorder.none,
hintText: 'Email',
contentPadding:
EdgeInsets.only(left: 70.0, top: 15.0),
hintStyle:
TextStyle(color: Colors.black, fontSize: 14.0)),
style: TextStyle(color: Colors.black),
),
)
],
),
new Column(children: <Widget>[
Container(
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(
width: 0.5, color: Colors.black))),
)
]),
],
),
),
),
actions: <Widget>[
new FlatButton(
child: new Text(
'CANCEL',
style: TextStyle(color: Colors.black),
),
onPressed: () {
Navigator.of(context).pop("");
},
),
new FlatButton(
child: new Text(
'SEND EMAIL',
style: TextStyle(color: Colors.black),
),
onPressed: () {
if (_sendResetEmail()) {
Navigator.of(context).pop(_resetEmail);
}
},
),
],
),
);
}
}
String validateEmail(String value) {
String pattern =
r'^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regExp = new RegExp(pattern);
if (value.length == 0) {
return "Email is required";
} else if (!regExp.hasMatch(value)) {
return "Invalid Email";
} else {
return null;
}
}
我不得不提取 validateEmail()
方法以使其可用于新的小部件。
我知道这个post来不及了,但我想分享我的代码,以便同汤中的任何人都能从中得到帮助。该表单也使用正则表达式过滤器进行实时验证。
以下代码片段改编自 chemamolins and from this blog 提供的解决方案。
final TextEditingController _nameController = TextEditingController();
final TextEditingController _phoneController = TextEditingController();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final GlobalKey<FormFieldState> _nameFormKey = GlobalKey<FormFieldState>();
final GlobalKey<FormFieldState> _phoneFormKey = GlobalKey<FormFieldState>();
bool _isFormValid() {
return ((_nameFormKey.currentState.isValid &&
_phoneFormKey.currentState.isValid));
}
void _submit() {
print('Name: ' +
_nameController.text +
', problem: ' +
_phoneController.text);
}
Future<void> _registerDialogBox(BuildContext context) async {
return await showDialog<String>(
context: context,
barrierDismissible: false,
builder: (context) {
bool _isSubmitButtonEnabled = false;
return StatefulBuilder(builder: (context, setState) {
return AlertDialog(
scrollable: true,
title: Text('Register'),
content: Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
new TextFormField(
key: _nameFormKey,
controller: _nameController,
maxLength: 30,
maxLengthEnforced: true,
keyboardType: TextInputType.name,
inputFormatters: [new FilteringTextInputFormatter.allow(RegExp("[a-zA-Z]")), ],
decoration: InputDecoration(
labelText: 'Name',
icon: Icon(Icons.account_box),
),
onChanged: (value) {
setState(() {
_isSubmitButtonEnabled = _isFormValid();
_nameFormKey.currentState.validate();
});
},
validator: (value) {
if (value.length < 3 )
return 'Min 3 and Max 30 characters';
else
return null;
}),
new TextFormField(
key: _phoneFormKey,
maxLength: 13,
maxLengthEnforced: true,
controller: _phoneController,
keyboardType: TextInputType.phone,
inputFormatters: [new FilteringTextInputFormatter.allow(RegExp("[0-9+]"))],
decoration: InputDecoration(
labelText: 'Phone',
icon: Icon(Icons.phone),
),
onChanged: (value) {
setState(() {
_isSubmitButtonEnabled = _isFormValid();
_phoneFormKey.currentState.validate();
});
},
validator: (value) {
if (value.length > 1 && value.length < 10 )
return 'Minimum 10 digits';
else
return null;
}),
],
),
),
),
actions: [
RaisedButton(
child: Text("Submit"),
onPressed: _isSubmitButtonEnabled ? () => _submit() : null)
],
);
});
});
}
现在在 Widget build
中,可以这样调用 Future
函数:
Padding(
padding: EdgeInsets.only(top: 5.0, bottom: 20.0),
child: RaisedButton(onPressed: ()async {
await _registerDialogBox(context);
}),
),