所以,这是我的表单小部件的相关部分。
// email
section = Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// heading section
Container(
width: _formSectionHeadingSize.width,
height: _formSectionHeadingSize.height,
child: Center(
child: Text(
"Your Email Address",
style: Theme.of(context).textTheme.headline,
),
),
),
// description
Container(
width: _formSectionDescriptionSize.width,
height: _formSectionDescriptionSize.height,
padding: const EdgeInsets.all(_sectionPadding),
child: Text(
"Your email address will be used to send you purchase receipts, updates, and other information.",
style: Theme.of(context).textTheme.body1,
),
),
// body
Container(
width: _formSectionBodySize.width,
height: _formSectionBodySize.height,
padding: const EdgeInsets.all(_sectionPadding),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
controller: _email,
focusNode: _emailFocus,
textInputAction: TextInputAction.done,
keyboardType: TextInputType.emailAddress,
onChanged: (value) {
BlocProvider.of<RegistrationFormBloc>(context)
.add(EmailChanged(email: value));
},
onFieldSubmitted: (value) {
FocusScope.of(context).unfocus();
BlocProvider.of<RegistrationFormBloc>(context)
.add(EmailChanged(email: value));
},
decoration: InputDecoration(
labelText: "Email Address",
labelStyle: Theme.of(context).textTheme.body1,
hintText: "example@example.com",
errorText: currentState.emailError,
),
),
],
),
),
// navigation
Container(
width: _bottomNavigationSize.width,
height: _bottomNavigationSize.height,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
FlatButton(
child: Text(
"Back",
style: Theme.of(context).textTheme.body1,
),
onPressed: this._showPreviousSection,
),
FlatButton(
child: Text(
"Next",
style: Theme.of(context).textTheme.body1,
),
onPressed: currentState.emailFieldsAreValid()
? this._showNextSection
: null,
),
],
),
),
],
);
每次电子邮件字段更改时,我都会EmailChanged
向我的 RegistrationFormBloc 添加一个事件来验证该值。这是集团逻辑。
@override
Stream<RegistrationState> mapEventToState(RegistrationEvent event) async* {
// The first name was updated.
if (event is FirstNameChanged) {
yield await this._onFirstNameChanged(event);
}
// Last name was changed
if (event is LastNameChanged) {
yield await this._onLastNameChanged(event);
}
// email was changed
if (event is EmailChanged) {
yield await this._onEmailChanged(event);
}
// Password was changed
if (event is PasswordChanged) {
yield await this._onPasswordChanged(event);
}
// The terms of service accept status has been changed
if (event is TermsAcceptanceChanged) {
yield await this._onTermsAccptanceChanged(event);
}
if (event is PrivacyAcceptanceChanged) {
yield await this._onPrivacyAcceptanceChanged(event);
}
// The form is submitted
if (event is FormSubmitted) {
yield RegistrationProcessing();
yield await this._onFormSubmitted(event);
}
// date of birth has been changed.
if (event is DateOfBirthChanged) {
yield await this._onDateOfBirthChanged(event);
}
// form started
if (event is FormStarted) {
yield await this._onFormStarted(event);
}
// form canceled by the user
if (event is FormCanceled) {
yield await this._onFormCanceled(event);
yield RegistrationUninitialized();
}
}
这是该_onEmailChanged()
功能的 logoc。
Future<RegistrationState> _onEmailChanged(EmailChanged event) async {
return RegistrationInProgress(
firstName: state.firstName,
firstNameError: state.firstNameError,
lastName: state.lastName,
lastNameError: state.lastNameError,
email: event.email,
emailError: await this._validateEmail(event.email),
password: state.password,
passwordError: state.passwordError,
dob: state.dob,
dobError: state.dobError,
acceptedTerms: state.acceptedTerms,
acceptedPrivacy: state.acceptedPrivacy,
);
}
这里相关的是emailError
字段。这是该validateEmail()
函数的代码。
Future<String> _validateEmail(String email) async {
// make sure the email is not empty.
if (QuiverString.isBlank(email)) {
return "This field is required";
}
// make sure the email is of a valid form
if (!this._emailIsValidForm(email)) {
return "Email is invalid.";
}
// make sure the email is not already taken by another account.
if ( !await this._emailIsAvailable(email)) {
return "email already in use";
}
return null;
}
似乎导致错误的行是emailIsValidForm()
函数(因为返回了响应错误)。这是该功能的代码。我使用EmailValidator
包来验证电子邮件。
bool _emailIsValidForm(String email) {
return EmailValidator.validate(email, ALLOW_TOP_LEVEL_DOMAINS, ALLOW_INTERNATIONAL);
}
我遇到的问题是即使我输入了有效的电子邮件地址,错误字段也会显示“电子邮件无效”。
关于造成这种情况的任何建议?
这是我的状态和事件类。
abstract class RegistrationState extends Equatable {
final String firstName;
final String firstNameError;
final String lastName;
final String lastNameError;
final String email;
final String emailError;
final String password;
final String passwordError;
final DateTime dob;
final String dobError;
final bool acceptedTerms;
final bool acceptedPrivacy;
final String registrationError;
@override
List<Object> get props => [
firstName,
firstNameError,
lastName,
lastNameError,
email,
emailError,
password,
passwordError,
dob,
dobError,
acceptedTerms,
acceptedPrivacy,
registrationError
];
const RegistrationState({
@required this.firstName,
@required this.firstNameError,
@required this.lastName,
@required this.lastNameError,
@required this.email,
@required this.emailError,
@required this.password,
@required this.passwordError,
@required this.dob,
@required this.dobError,
@required this.acceptedTerms,
@required this.acceptedPrivacy,
this.registrationError,
});
// make sure the name fields are valid.
bool nameFieldsAreValid() {
return (firstNameError == null) && (lastNameError == null) && (firstName != null) && (lastName != null);
}
// checks if the email field is valid.
bool emailFieldsAreValid() {
return (emailError == null) && (email != null);
}
// make sure the password fields are valid.
bool passwordFieldsAreVaid() {
return (passwordError == null) && (password != null);
}
// chekcs to see if the date of birth is valid.
bool dobFiedsAreValid() {
return ((dob != null) && (dobError == null));
}
// validates that all the terms have been accepted.
bool allTermsAccepted() {
return (acceptedTerms && acceptedPrivacy);
}
}
/// Uninitialized State
///
/// The Registration has not been invoked.
class RegistrationUninitialized extends RegistrationState {
RegistrationUninitialized()
: super(
firstName: null,
firstNameError: null,
lastName: null,
lastNameError: null,
email: null,
emailError: null,
password: null,
passwordError: null,
dob: null,
dobError: null,
acceptedTerms: false,
acceptedPrivacy: false,
registrationError: null);
}
/// Started State
///
/// The Registration process has been initiated and is in progress.
class RegistrationStarted extends RegistrationState {
RegistrationStarted()
: super(
firstName: null,
firstNameError: null,
lastName: null,
lastNameError: null,
email: null,
emailError: null,
password: null,
passwordError: null,
dob: null,
dobError: null,
acceptedTerms: false,
acceptedPrivacy: false,
registrationError: null);
}
/// InProgress State
///
/// The Registration application is in progress.
class RegistrationInProgress extends RegistrationState {
RegistrationInProgress(
{@required firstName,
@required firstNameError,
String lastName,
String lastNameError,
@required email,
@required String emailError,
@required String password,
@required String passwordError,
@required DateTime dob,
@required String dobError,
@required bool acceptedTerms,
@required bool acceptedPrivacy})
: super(
firstName: firstName,
firstNameError: firstNameError,
lastName: lastName,
lastNameError: lastNameError,
email: email,
emailError: emailError,
password: password,
passwordError: passwordError,
dob: dob,
dobError: dobError,
acceptedTerms: acceptedTerms,
acceptedPrivacy: acceptedPrivacy,
registrationError: null);
}
/// Completed State
///
/// The Registration has been completed successfully.
class RegistrationCompleted extends RegistrationState {
RegistrationCompleted(
{@required firstName,
@required firstNameError,
String lastName,
String lastNameError,
@required email,
@required String emailError,
@required String password,
@required String passwordError,
@required DateTime dob,
@required String dobError,
@required bool acceptedTerms,
@required bool acceptedPrivacy})
: super(
firstName: firstName,
firstNameError: firstNameError,
lastName: lastName,
lastNameError: lastNameError,
email: email,
emailError: emailError,
password: password,
passwordError: passwordError,
dob: dob,
dobError: dobError,
acceptedTerms: acceptedTerms,
acceptedPrivacy: acceptedPrivacy,
registrationError: null);
}
/// Rejected State
///
/// The registration has been rejected
class RegistrationRejected extends RegistrationState {
RegistrationRejected(
{@required firstName,
@required firstNameError,
String lastName,
String lastNameError,
@required email,
@required String emailError,
@required String password,
@required String passwordError,
@required DateTime dob,
@required String dobError,
@required bool acceptedTerms,
@required bool acceptedPrivacy,
@required String registrationError})
: super(
firstName: firstName,
firstNameError: firstNameError,
lastName: lastName,
lastNameError: lastNameError,
email: email,
emailError: emailError,
password: password,
passwordError: passwordError,
dob: dob,
dobError: dobError,
acceptedTerms: acceptedTerms,
acceptedPrivacy: acceptedPrivacy,
registrationError: registrationError);
}
/// Canceled State
///
/// The registration was canceled by the user.
class RegistrationCanceled extends RegistrationState {
RegistrationCanceled(
{@required firstName,
@required String lastName,
@required email,
@required DateTime dob,})
: super(
firstName: firstName,
firstNameError: null,
lastName: lastName,
lastNameError: null,
email: email,
emailError: null,
password: null,
passwordError: null,
dob: dob,
dobError: null,
acceptedTerms: true,
acceptedPrivacy: true,
registrationError: null);
}
/// Processing State
///
/// The registration is being processed by the server.
class RegistrationProcessing extends RegistrationState {}
这是我的事件代码。
abstract class RegistrationEvent extends Equatable {
const RegistrationEvent();
@override
List<Object> get props => [];
}
class FirstNameChanged extends RegistrationEvent {
final String firstName;
const FirstNameChanged({@required this.firstName});
@override
List<Object> get props => [firstName];
@override
String toString() {
return "FirstNameChanged Event: {\n$firstName\n}";
}
}
class LastNameChanged extends RegistrationEvent {
final String lastName;
const LastNameChanged({@required this.lastName});
@override
List<Object> get props => [lastName];
@override
String toString() {
return "LastNameChanged Event: {\n$lastName\n}";
}
}
class EmailChanged extends RegistrationEvent {
final String email;
const EmailChanged({@required this.email});
@override
List<Object> get props => [email];
@override
String toString() {
return "EmailChanged Event: {\n$email\n}";
}
}
class PasswordChanged extends RegistrationEvent {
final String password;
const PasswordChanged({@required this.password});
@override
List<Object> get props => [password];
@override
String toString() {
return "PasswordChanged Event: {\n${password.replaceRange(0, password.length - 1, "*")}\n}";
}
}
class TermsAcceptanceChanged extends RegistrationEvent {
final bool acceptTerms;
const TermsAcceptanceChanged({@required this.acceptTerms});
@override
List<Object> get props => [acceptTerms];
@override
String toString() {
String msg = acceptTerms ? "Accepted" : "Denied";
return "TermsAcceptanceChanged Event: {\n$msg\n}";
}
}
class PrivacyAcceptanceChanged extends RegistrationEvent {
final bool acceptPrivacy;
const PrivacyAcceptanceChanged({@required this.acceptPrivacy});
@override
List<Object> get props => [acceptPrivacy];
@override
String toString() {
String msg = acceptPrivacy ? "Accepted" : "Denied";
return "PrivacyAcceptanceChanged Event: {\n$msg\n}";
}
}
class FormSubmitted extends RegistrationEvent {
@override
String toString() {
return "FormSubmitted Event: {}";
}
}
class FormStarted extends RegistrationEvent {
@override
String toString() {
return "FormStarted Event: {}";
}
}
class FormCanceled extends RegistrationEvent {
@override
String toString() {
return "FormCanceled Event: {}";
}
}
class DateOfBirthChanged extends RegistrationEvent {
final DateTime dob;
const DateOfBirthChanged({@required this.dob});
@override
List<Object> get props => [dob];
@override
String toString() {
DateFormat format = DateFormat.yMd();
return "DateOfBirthChanged Event: {\n${format.format(dob)}\n}";
}
}
class FormProcessing extends RegistrationEvent {
@override
String toString() {
return "FormProcessing Event: {}";
}
}
这是_onFirstNameChange()
Future<RegistrationState> _onFirstNameChanged(FirstNameChanged event) async {
return RegistrationInProgress(
firstName: event.firstName,
firstNameError: this._validateFirstName(event.firstName),
lastName: state.lastName,
lastNameError: state.lastNameError,
email: state.email,
emailError: state.emailError,
password: state.password,
passwordError: state.passwordError,
dob: state.dob,
dobError: state.dobError,
acceptedTerms: state.acceptedTerms,
acceptedPrivacy: state.acceptedPrivacy);
}
和_validateFirstName()
方法。
String _validateFirstName(String fName) {
return QuiverString.isBlank(fName) ? "This field is required." : null;
}
还不如包括我的构建方法。
@override
Widget build(BuildContext context) {
return BlocBuilder<RegistrationFormBloc, RegistrationState>(
builder: (BuildContext context, RegistrationState state) {
return Scaffold(
body: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// the form itself.
_showFormSection(state),
// the exit button
Center(
child: FlatButton(
child: Text("Cancel"),
onPressed: () {
BlocProvider.of<RegistrationFormBloc>(context)
.add(FormCanceled());
Navigator.pop(context);
},
),
),
],
),
),
),
),
);
},
);
}
_showFormSection() 向用户显示适当的表单部分(名称部分、电子邮件部分等...),以使其具有“多页表单”效果。该函数只是根据当前索引返回适当的小部件。
Widget _showFormSection(RegistrationState currentState) {
Widget section;
switch (_pageIndex) {
case 0:
section = NameWidget();
break;
case 1:
// email
section = Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// heading section
Container(
width: _formSectionHeadingSize.width,
height: _formSectionHeadingSize.height,
child: Center(
child: Text(
"Your Email Address",
style: Theme.of(context).textTheme.headline,
),
),
),
// description
Container(
width: _formSectionDescriptionSize.width,
height: _formSectionDescriptionSize.height,
padding: const EdgeInsets.all(_sectionPadding),
child: Text(
"Your email address will be used to send you purchase receipts, updates, and other information.",
style: Theme.of(context).textTheme.body1,
),
),
// body
Container(
width: _formSectionBodySize.width,
height: _formSectionBodySize.height,
padding: const EdgeInsets.all(_sectionPadding),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
controller: _email,
focusNode: _emailFocus,
textInputAction: TextInputAction.done,
keyboardType: TextInputType.emailAddress,
onChanged: (value) {
BlocProvider.of<RegistrationFormBloc>(context)
.add(EmailChanged(email: value));
},
onFieldSubmitted: (value) {
FocusScope.of(context).unfocus();
BlocProvider.of<RegistrationFormBloc>(context)
.add(EmailChanged(email: value));
},
decoration: InputDecoration(
labelText: "Email Address",
labelStyle: Theme.of(context).textTheme.body1,
hintText: "example@example.com",
errorText: currentState.emailError,
),
),
],
),
),
// navigation
Container(
width: _bottomNavigationSize.width,
height: _bottomNavigationSize.height,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
FlatButton(
child: Text(
"Back",
style: Theme.of(context).textTheme.body1,
),
onPressed: this._showPreviousSection,
),
FlatButton(
child: Text(
"Next",
style: Theme.of(context).textTheme.body1,
),
onPressed: currentState.emailFieldsAreValid()
? this._showNextSection
: null,
),
],
),
),
],
);
break;
case 2:
// password
section = PasswordWidget();
break;
case 3:
// date of birth
section = DobWidget();
break;
case 4:
// terms section
section = TermsWidget();
break;
}
return section;
}
// show the next section.
void _showNextSection() {
if (_pageIndex < 4) {
setState(() {
_pageIndex++;
});
}
}
// show the previous section
void _showPreviousSection() {
if (_pageIndex > 0) {
setState(() {
_pageIndex--;
});
}
}