Email Validation not updating with Email textfield change

Issue

So, here’s the relevant part of my form widget.

// 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,
                  ),
                ],
              ),
            ),
          ],
        );

Every time the email field changes, I add an EmailChanged event to my RegistrationFormBloc to validate the value. Here’s the Bloc Logic.

@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();
    } 
  }

Here’s the logoc for the _onEmailChanged() function.

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,
    );
  }

The relevant thing here is the emailError field. Here’s the code for the validateEmail() function.

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;
  }

The line that seems to be causing the error is the emailIsValidForm() function (because the response error returned). Here’s the code for that function. I use the EmailValidator package to validate the email.

bool _emailIsValidForm(String email) {
    return EmailValidator.validate(email, ALLOW_TOP_LEVEL_DOMAINS, ALLOW_INTERNATIONAL);
  }

The problem I am encountering is even if I enter a valid email address, the error field says “Email Invalid”.

Any advice on what is causing this?

Here’s my state and event classes btw.

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 {}

And here is my Events code.

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: {}";
  }
}

Here is _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);
  }

And the _validateFirstName() method.

String _validateFirstName(String fName) {
    return QuiverString.isBlank(fName) ? "This field is required." : null;
  }

Might as well also include my build method.

@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() displays the appropriate form section to the user(name section, email section, etc…) to give it that “Multi-Page form” effect. The function just returns the appropriate widget according to the current index.

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--;
      });
    }
  }

Solution

In onchanged, pass value.trim() to the event. Sometimes there is trailling space in inputtext after u ve enter ur email, to remove that when validating , use the function trim so that it will remove that space.

Answered By – loicgeek

Answer Checked By – Jay B. (FlutterFixes Admin)

Leave a Reply

Your email address will not be published.