Repository: felangel/bloc

Issue:
fix(flutter_firebase_login): Bug in `LoginForm` BlocListener causing repeated snackbar messages
**Description**
There's a bug in the login form where error messages continually reappear when users try to correct their credentials after a failed login attempt. After an initial authentication failure, each keystroke in either the email or password field triggers another error snackbar.

**Steps To Reproduce**

1. Enter incorrect login credentials
2. Press the login button
3. Authentication fails and an error snackbar appears
4. Start typing in the email or password field to correct credentials
5. A new error snackbar appears on every keystroke

**Expected Behavior**
After a failed login attempt, the error message should appear once. When the user begins making corrections to their credentials, the error message should be dismissed and should not reappear until another login attempt fails.

**Proposed Solution**
It can be fixed by adding `listenWhen: (previous, current) => previous.status != current.status && current.status.isFailure` check to the `BlockListener` in the `LoginForm`.
fix: bug in the firebase login example
I believe the [firebase login example](https://github.com/felangel/bloc/tree/master/examples/flutter_firebase_login) has a bug.

In case there's a login error, the bloc is emitting a new state with `status: FormzSubmissionStatus.failure`, and this state triggers a snackbar with a message.

If we change the email or password, every time we change them will trigger a new state with `copyWith`, so the `FormzSubmissionStatus.failure` status will be carried with the `copyWith`, triggering more snackbar messages, even though there might not be any errors with the email or password.

I think this example lacks an example on how to reset the status, so we don't keep triggering it.

I can see this being made in some ways:

1. After the snackbar is shown, the view calls a method in the cubit to reset the status.
1. We can emit 2 states at once, the first has the failure status, the second the status is reset. This will trigger the listener twice.
1. We can remember to set the status each time we emit a new state.

I find option `1` the best. Option `2` looks kinda weird, emitting 2 states at once, and can trigger more things in the listener if we are not careful with the listener code, so it could have side effects. Option `3` is very fragile in my opinion, it's very easy to forget to add a status in one of the state emissions.

I would like to know your thoughts regarding what's the best practice for resetting states @felangel, thanks!


Discussion: This also occurs on `SignUpForm` which can be triggered by trying to create an account with an email that is already in use and can be fixed with the previously proposed solution.
I think the `BlocListener` for the `LoginState` status should provide a `listenWhen` to prevent the SnackBar on spamming whenever there's a state changes coming from other props.
I think this is helpful for many case, but I don't think this cover the case where we need to show the same error message again. If we don't clear it, the string will appear to be same and the error message won't appear. 
I mean, validate the  `errorMessage`. Something like this:
```
BlocListener<LoginCubit, LoginState>(
      listenWhen: (previous, current) => previous.errorMessage != current.errorMessage 
        && current.errorMessage != null,
      listener: (context, state) {
        if (state.status.isFailure) {
          ScaffoldMessenger.of(context)
            ..hideCurrentSnackBar()
            ..showSnackBar(
              SnackBar(
                content: Text(state.errorMessage ?? 'Authentication Failure'),
              ),
            );
        }
      },

```

Yeah, but how about when the user typed an invalid e email, then they fix it to a valid email, then they type an invalid email again?

If you don't remember to clear the error message when you have a valid email, you won't fire the snackbar when the user types the wrong email for the second time. 
By convention, that's a form-level error, based on the example app you referenced too. 
So in my opinion, you should trigger the `error` field of a `TextFormField` instead of showing a `SnackBar`.
Ok, but that doesn't change the case here, where we show the error, or any kind of event doesn't change how events are managed.

We use bloc to separate the UI logic from the UI visualization. So the same event from the bloc can appear in the screen as a text field error, or a snackbar, or an alert, or a navigation to another page, or a red icon. That's the beauty of separating the screen from the bloc.

My point is, your solution doesn't fix the problem, because even if we filter the event, if we are not careful to reset it, it won't fire a second time, if you need an event to fire a second time. That's the main problem, and here we have a specific example of the problem in a form, but don't get too attached to the example. 
I'm not filtering the event, I'm filtering the expected state, making it predictable. 

The easiest and clean approach for me is the second. Reset the status back to _initial_ inside the `finally` clause after it emit _failure_ since the "goal" of the `catch` clause is to simply tell the ui that there's something wrong happened within the bloc layer. We can prevent the `BlocListener` to be triggered twice by providing the `listenWhen`.

Let's see how Felix will answer this for convention.

@fernan542 could you provide an example of resetting the status?

I may be wrong because I'm new to this (still going through this login tutorial when I found this bug) but this seems like something better suited to Blocs instead of Cubits so that we could show the snackbar on the transition event.

Fix patch:
diff --git a/examples/flutter_firebase_login/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/examples/flutter_firebase_login/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index 8e3ca5dfe19..15cada4838e 100644
--- a/examples/flutter_firebase_login/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/examples/flutter_firebase_login/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -59,6 +59,7 @@
       ignoresPersistentStateOnLaunch = "NO"
       debugDocumentVersioning = "YES"
       debugServiceExtension = "internal"
+      enableGPUValidationMode = "1"
       allowLocationSimulation = "YES">
       <BuildableProductRunnable
          runnableDebuggingMode = "0">
diff --git a/examples/flutter_firebase_login/lib/login/cubit/login_cubit.dart b/examples/flutter_firebase_login/lib/login/cubit/login_cubit.dart
index 37594b925e1..8f0b2bd1df1 100644
--- a/examples/flutter_firebase_login/lib/login/cubit/login_cubit.dart
+++ b/examples/flutter_firebase_login/lib/login/cubit/login_cubit.dart
@@ -11,61 +11,35 @@ class LoginCubit extends Cubit<LoginState> {
 
   final AuthenticationRepository _authenticationRepository;
 
-  void emailChanged(String value) {
-    final email = Email.dirty(value);
-    emit(
-      state.copyWith(
-        email: email,
-        isValid: Formz.validate([email, state.password]),
-      ),
-    );
-  }
+  void emailChanged(String email) => emit(state.withEmail(email));
 
-  void passwordChanged(String value) {
-    final password = Password.dirty(value);
-    emit(
-      state.copyWith(
-        password: password,
-        isValid: Formz.validate([state.email, password]),
-      ),
-    );
-  }
+  void passwordChanged(String password) => emit(state.withPassword(password));
 
   Future<void> logInWithCredentials() async {
     if (!state.isValid) return;
-    emit(state.copyWith(status: FormzSubmissionStatus.inProgress));
+    emit(state.withSubmissionInProgress());
     try {
       await _authenticationRepository.logInWithEmailAndPassword(
         email: state.email.value,
         password: state.password.value,
       );
-      emit(state.copyWith(status: FormzSubmissionStatus.success));
+      emit(state.withSubmissionSuccess());
     } on LogInWithEmailAndPasswordFailure catch (e) {
-      emit(
-        state.copyWith(
-          errorMessage: e.message,
-          status: FormzSubmissionStatus.failure,
-        ),
-      );
+      emit(state.withSubmissionFailure(e.message));
     } catch (_) {
-      emit(state.copyWith(status: FormzSubmissionStatus.failure));
+      emit(state.withSubmissionFailure());
     }
   }
 
   Future<void> logInWithGoogle() async {
-    emit(state.copyWith(status: FormzSubmissionStatus.inProgress));
+    emit(state.withSubmissionInProgress());
     try {
       await _authenticationRepository.logInWithGoogle();
-      emit(state.copyWith(status: FormzSubmissionStatus.success));
+      emit(state.withSubmissionSuccess());
     } on LogInWithGoogleFailure catch (e) {
-      emit(
-        state.copyWith(
-          errorMessage: e.message,
-          status: FormzSubmissionStatus.failure,
-        ),
-      );
+      emit(state.withSubmissionFailure(e.message));
     } catch (_) {
-      emit(state.copyWith(status: FormzSubmissionStatus.failure));
+      emit(state.withSubmissionFailure());
     }
   }
 }
diff --git a/examples/flutter_firebase_login/lib/login/cubit/login_state.dart b/examples/flutter_firebase_login/lib/login/cubit/login_state.dart
index b3dd883b62c..2e32197620b 100644
--- a/examples/flutter_firebase_login/lib/login/cubit/login_state.dart
+++ b/examples/flutter_firebase_login/lib/login/cubit/login_state.dart
@@ -1,36 +1,55 @@
 part of 'login_cubit.dart';
 
 final class LoginState extends Equatable {
-  const LoginState({
+  const LoginState() : this._();
+
+  const LoginState._({
     this.email = const Email.pure(),
     this.password = const Password.pure(),
     this.status = FormzSubmissionStatus.initial,
-    this.isValid = false,
     this.errorMessage,
   });
 
+  LoginState withEmail(String email) {
+    return LoginState._(email: Email.dirty(email), password: password);
+  }
+
+  LoginState withPassword(String password) {
+    return LoginState._(email: email, password: Password.dirty(password));
+  }
+
+  LoginState withSubmissionInProgress() {
+    return LoginState._(
+      email: email,
+      password: password,
+      status: FormzSubmissionStatus.inProgress,
+    );
+  }
+
+  LoginState withSubmissionSuccess() {
+    return LoginState._(
+      email: email,
+      password: password,
+      status: FormzSubmissionStatus.success,
+    );
+  }
+
+  LoginState withSubmissionFailure([String? error]) {
+    return LoginState._(
+      email: email,
+      password: password,
+      status: FormzSubmissionStatus.failure,
+      errorMessage: error,
+    );
+  }
+
   final Email email;
   final Password password;
   final FormzSubmissionStatus status;
-  final bool isValid;
   final String? errorMessage;
 
+  bool get isValid => Formz.validate([email, password]);
+
   @override
-  List<Object?> get props => [email, password, status, isValid, errorMessage];
-
-  LoginState copyWith({
-    Email? email,
-    Password? password,
-    FormzSubmissionStatus? status,
-    bool? isValid,
-    String? errorMessage,
-  }) {
-    return LoginState(
-      email: email ?? this.email,
-      password: password ?? this.password,
-      status: status ?? this.status,
-      isValid: isValid ?? this.isValid,
-      errorMessage: errorMessage ?? this.errorMessage,
-    );
-  }
+  List<Object?> get props => [email, password, status, errorMessage];
 }
diff --git a/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_cubit.dart b/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_cubit.dart
index 657352c3a31..55500e528c3 100644
--- a/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_cubit.dart
+++ b/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_cubit.dart
@@ -11,74 +11,27 @@ class SignUpCubit extends Cubit<SignUpState> {
 
   final AuthenticationRepository _authenticationRepository;
 
-  void emailChanged(String value) {
-    final email = Email.dirty(value);
-    emit(
-      state.copyWith(
-        email: email,
-        isValid: Formz.validate([
-          email,
-          state.password,
-          state.confirmedPassword,
-        ]),
-      ),
-    );
-  }
+  void emailChanged(String email) => emit(state.withEmail(email));
 
-  void passwordChanged(String value) {
-    final password = Password.dirty(value);
-    final confirmedPassword = ConfirmedPassword.dirty(
-      password: password.value,
-      value: state.confirmedPassword.value,
-    );
-    emit(
-      state.copyWith(
-        password: password,
-        confirmedPassword: confirmedPassword,
-        isValid: Formz.validate([
-          state.email,
-          password,
-          confirmedPassword,
-        ]),
-      ),
-    );
-  }
+  void passwordChanged(String password) => emit(state.withPassword(password));
 
-  void confirmedPasswordChanged(String value) {
-    final confirmedPassword = ConfirmedPassword.dirty(
-      password: state.password.value,
-      value: value,
-    );
-    emit(
-      state.copyWith(
-        confirmedPassword: confirmedPassword,
-        isValid: Formz.validate([
-          state.email,
-          state.password,
-          confirmedPassword,
-        ]),
-      ),
-    );
+  void confirmedPasswordChanged(String confirmedPassword) {
+    emit(state.withConfirmedPassword(confirmedPassword));
   }
 
   Future<void> signUpFormSubmitted() async {
     if (!state.isValid) return;
-    emit(state.copyWith(status: FormzSubmissionStatus.inProgress));
+    emit(state.withSubmissionInProgress());
     try {
       await _authenticationRepository.signUp(
         email: state.email.value,
         password: state.password.value,
       );
-      emit(state.copyWith(status: FormzSubmissionStatus.success));
+      emit(state.withSubmissionSuccess());
     } on SignUpWithEmailAndPasswordFailure catch (e) {
-      emit(
-        state.copyWith(
-          errorMessage: e.message,
-          status: FormzSubmissionStatus.failure,
-        ),
-      );
+      emit(state.withSubmissionFailure(e.message));
     } catch (_) {
-      emit(state.copyWith(status: FormzSubmissionStatus.failure));
+      emit(state.withSubmissionFailure());
     }
   }
 }
diff --git a/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_state.dart b/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_state.dart
index 24b8d1df953..20207d3e4a5 100644
--- a/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_state.dart
+++ b/examples/flutter_firebase_login/lib/sign_up/cubit/sign_up_state.dart
@@ -1,12 +1,13 @@
 part of 'sign_up_cubit.dart';
 
 final class SignUpState extends Equatable {
-  const SignUpState({
+  const SignUpState() : this._();
+
+  const SignUpState._({
     this.email = const Email.pure(),
     this.password = const Password.pure(),
     this.confirmedPassword = const ConfirmedPassword.pure(),
     this.status = FormzSubmissionStatus.initial,
-    this.isValid = false,
     this.errorMessage,
   });
 
@@ -14,34 +15,71 @@ final class SignUpState extends Equatable {
   final Password password;
   final ConfirmedPassword confirmedPassword;
   final FormzSubmissionStatus status;
-  final bool isValid;
   final String? errorMessage;
 
+  SignUpState withEmail(String email) {
+    return SignUpState._(
+      email: Email.dirty(email),
+      password: password,
+      confirmedPassword: confirmedPassword,
+    );
+  }
+
+  SignUpState withPassword(String password) {
+    return SignUpState._(
+      email: email,
+      password: Password.dirty(password),
+      confirmedPassword: confirmedPassword,
+    );
+  }
+
+  SignUpState withConfirmedPassword(String confirmedPassword) {
+    return SignUpState._(
+      email: email,
+      password: password,
+      confirmedPassword: ConfirmedPassword.dirty(
+        password: password.value,
+        value: confirmedPassword,
+      ),
+    );
+  }
+
+  SignUpState withSubmissionInProgress() {
+    return SignUpState._(
+      email: email,
+      password: password,
+      confirmedPassword: confirmedPassword,
+      status: FormzSubmissionStatus.inProgress,
+    );
+  }
+
+  SignUpState withSubmissionSuccess() {
+    return SignUpState._(
+      email: email,
+      password: password,
+      confirmedPassword: confirmedPassword,
+      status: FormzSubmissionStatus.success,
+    );
+  }
+
+  SignUpState withSubmissionFailure([String? error]) {
+    return SignUpState._(
+      email: email,
+      password: password,
+      confirmedPassword: confirmedPassword,
+      status: FormzSubmissionStatus.failure,
+      errorMessage: error,
+    );
+  }
+
+  bool get isValid => Formz.validate([email, password, confirmedPassword]);
+
   @override
   List<Object?> get props => [
         email,
         password,
         confirmedPassword,
         status,
-        isValid,
         errorMessage,
       ];
-
-  SignUpState copyWith({
-    Email? email,
-    Password? password,
-    ConfirmedPassword? confirmedPassword,
-    FormzSubmissionStatus? status,
-    bool? isValid,
-    String? errorMessage,
-  }) {
-    return SignUpState(
-      email: email ?? this.email,
-      password: password ?? this.password,
-      confirmedPassword: confirmedPassword ?? this.confirmedPassword,
-      status: status ?? this.status,
-      isValid: isValid ?? this.isValid,
-      errorMessage: errorMessage ?? this.errorMessage,
-    );
-  }
 }


Test patch:
diff --git a/examples/flutter_firebase_login/test/login/cubit/login_cubit_test.dart b/examples/flutter_firebase_login/test/login/cubit/login_cubit_test.dart
index 62e64fcc93d..31b413144e2 100644
--- a/examples/flutter_firebase_login/test/login/cubit/login_cubit_test.dart
+++ b/examples/flutter_firebase_login/test/login/cubit/login_cubit_test.dart
@@ -3,25 +3,16 @@ import 'package:authentication_repository/authentication_repository.dart';
 import 'package:bloc_test/bloc_test.dart';
 import 'package:flutter_firebase_login/login/login.dart';
 import 'package:flutter_test/flutter_test.dart';
-import 'package:form_inputs/form_inputs.dart';
-import 'package:formz/formz.dart';
 import 'package:mocktail/mocktail.dart';
 
 class MockAuthenticationRepository extends Mock
     implements AuthenticationRepository {}
 
 void main() {
-  const invalidEmailString = 'invalid';
-  const invalidEmail = Email.dirty(invalidEmailString);
-
-  const validEmailString = 'test@gmail.com';
-  const validEmail = Email.dirty(validEmailString);
-
-  const invalidPasswordString = 'invalid';
-  const invalidPassword = Password.dirty(invalidPasswordString);
-
-  const validPasswordString = 't0pS3cret1234';
-  const validPassword = Password.dirty(validPasswordString);
+  const invalidEmail = 'invalid';
+  const validEmail = 'test@gmail.com';
+  const invalidPassword = 'invalid';
+  const validPassword = 't0pS3cret1234';
 
   group('LoginCubit', () {
     late AuthenticationRepository authenticationRepository;
@@ -47,21 +38,17 @@ void main() {
       blocTest<LoginCubit, LoginState>(
         'emits [invalid] when email/password are invalid',
         build: () => LoginCubit(authenticationRepository),
-        act: (cubit) => cubit.emailChanged(invalidEmailString),
-        expect: () => const <LoginState>[LoginState(email: invalidEmail)],
+        act: (cubit) => cubit.emailChanged(invalidEmail),
+        expect: () => <LoginState>[LoginState().withEmail(invalidEmail)],
       );
 
       blocTest<LoginCubit, LoginState>(
         'emits [valid] when email/password are valid',
         build: () => LoginCubit(authenticationRepository),
-        seed: () => LoginState(password: validPassword),
-        act: (cubit) => cubit.emailChanged(validEmailString),
-        expect: () => const <LoginState>[
-          LoginState(
-            email: validEmail,
-            password: validPassword,
-            isValid: true,
-          ),
+        seed: () => LoginState().withPassword(validPassword),
+        act: (cubit) => cubit.emailChanged(validEmail),
+        expect: () => <LoginState>[
+          LoginState().withEmail(validEmail).withPassword(validPassword),
         ],
       );
     });
@@ -70,21 +57,17 @@ void main() {
       blocTest<LoginCubit, LoginState>(
         'emits [invalid] when email/password are invalid',
         build: () => LoginCubit(authenticationRepository),
-        act: (cubit) => cubit.passwordChanged(invalidPasswordString),
-        expect: () => const <LoginState>[LoginState(password: invalidPassword)],
+        act: (cubit) => cubit.passwordChanged(invalidPassword),
+        expect: () => <LoginState>[LoginState().withPassword(invalidPassword)],
       );
 
       blocTest<LoginCubit, LoginState>(
         'emits [valid] when email/password are valid',
         build: () => LoginCubit(authenticationRepository),
-        seed: () => LoginState(email: validEmail),
-        act: (cubit) => cubit.passwordChanged(validPasswordString),
-        expect: () => const <LoginState>[
-          LoginState(
-            email: validEmail,
-            password: validPassword,
-            isValid: true,
-          ),
+        seed: () => LoginState().withEmail(validEmail),
+        act: (cubit) => cubit.passwordChanged(validPassword),
+        expect: () => <LoginState>[
+          LoginState().withEmail(validEmail).withPassword(validPassword),
         ],
       );
     });
@@ -100,17 +83,15 @@ void main() {
       blocTest<LoginCubit, LoginState>(
         'calls logInWithEmailAndPassword with correct email/password',
         build: () => LoginCubit(authenticationRepository),
-        seed: () => LoginState(
-          email: validEmail,
-          password: validPassword,
-          isValid: true,
-        ),
+        seed: () {
+          return LoginState().withEmail(validEmail).withPassword(validPassword);
+        },
         act: (cubit) => cubit.logInWithCredentials(),
         verify: (_) {
           verify(
             () => authenticationRepository.logInWithEmailAndPassword(
-              email: validEmailString,
-              password: validPasswordString,
+              email: validEmail,
+              password: validPassword,
             ),
           ).called(1);
         },
@@ -120,25 +101,19 @@ void main() {
         'emits [submissionInProgress, submissionSuccess] '
         'when logInWithEmailAndPassword succeeds',
         build: () => LoginCubit(authenticationRepository),
-        seed: () => LoginState(
-          email: validEmail,
-          password: validPassword,
-          isValid: true,
-        ),
+        seed: () {
+          return LoginState().withEmail(validEmail).withPassword(validPassword);
+        },
         act: (cubit) => cubit.logInWithCredentials(),
-        expect: () => const <LoginState>[
-          LoginState(
-            status: FormzSubmissionStatus.inProgress,
-            email: validEmail,
-            password: validPassword,
-            isValid: true,
-          ),
-          LoginState(
-            status: FormzSubmissionStatus.success,
-            email: validEmail,
-            password: validPassword,
-            isValid: true,
-          ),
+        expect: () => <LoginState>[
+          LoginState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withSubmissionInProgress(),
+          LoginState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withSubmissionSuccess(),
         ],
       );
 
@@ -155,26 +130,19 @@ void main() {
           ).thenThrow(LogInWithEmailAndPasswordFailure('oops'));
         },
         build: () => LoginCubit(authenticationRepository),
-        seed: () => LoginState(
-          email: validEmail,
-          password: validPassword,
-          isValid: true,
-        ),
+        seed: () {
+          return LoginState().withEmail(validEmail).withPassword(validPassword);
+        },
         act: (cubit) => cubit.logInWithCredentials(),
-        expect: () => const <LoginState>[
-          LoginState(
-            status: FormzSubmissionStatus.inProgress,
-            email: validEmail,
-            password: validPassword,
-            isValid: true,
-          ),
-          LoginState(
-            status: FormzSubmissionStatus.failure,
-            errorMessage: 'oops',
-            email: validEmail,
-            password: validPassword,
-            isValid: true,
-          ),
+        expect: () => <LoginState>[
+          LoginState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withSubmissionInProgress(),
+          LoginState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withSubmissionFailure('oops'),
         ],
       );
 
@@ -190,25 +158,19 @@ void main() {
           ).thenThrow(Exception('oops'));
         },
         build: () => LoginCubit(authenticationRepository),
-        seed: () => LoginState(
-          email: validEmail,
-          password: validPassword,
-          isValid: true,
-        ),
+        seed: () {
+          return LoginState().withEmail(validEmail).withPassword(validPassword);
+        },
         act: (cubit) => cubit.logInWithCredentials(),
-        expect: () => const <LoginState>[
-          LoginState(
-            status: FormzSubmissionStatus.inProgress,
-            email: validEmail,
-            password: validPassword,
-            isValid: true,
-          ),
-          LoginState(
-            status: FormzSubmissionStatus.failure,
-            email: validEmail,
-            password: validPassword,
-            isValid: true,
-          ),
+        expect: () => <LoginState>[
+          LoginState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withSubmissionInProgress(),
+          LoginState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withSubmissionFailure(),
         ],
       );
     });
@@ -228,9 +190,9 @@ void main() {
         'when logInWithGoogle succeeds',
         build: () => LoginCubit(authenticationRepository),
         act: (cubit) => cubit.logInWithGoogle(),
-        expect: () => const <LoginState>[
-          LoginState(status: FormzSubmissionStatus.inProgress),
-          LoginState(status: FormzSubmissionStatus.success),
+        expect: () => <LoginState>[
+          LoginState().withSubmissionInProgress(),
+          LoginState().withSubmissionSuccess(),
         ],
       );
 
@@ -244,12 +206,9 @@ void main() {
         },
         build: () => LoginCubit(authenticationRepository),
         act: (cubit) => cubit.logInWithGoogle(),
-        expect: () => const <LoginState>[
-          LoginState(status: FormzSubmissionStatus.inProgress),
-          LoginState(
-            status: FormzSubmissionStatus.failure,
-            errorMessage: 'oops',
-          ),
+        expect: () => <LoginState>[
+          LoginState().withSubmissionInProgress(),
+          LoginState().withSubmissionFailure('oops'),
         ],
       );
 
@@ -263,9 +222,9 @@ void main() {
         },
         build: () => LoginCubit(authenticationRepository),
         act: (cubit) => cubit.logInWithGoogle(),
-        expect: () => const <LoginState>[
-          LoginState(status: FormzSubmissionStatus.inProgress),
-          LoginState(status: FormzSubmissionStatus.failure),
+        expect: () => <LoginState>[
+          LoginState().withSubmissionInProgress(),
+          LoginState().withSubmissionFailure(),
         ],
       );
     });
diff --git a/examples/flutter_firebase_login/test/login/cubit/login_state_test.dart b/examples/flutter_firebase_login/test/login/cubit/login_state_test.dart
index 95c9228c325..4eec0554b6e 100644
--- a/examples/flutter_firebase_login/test/login/cubit/login_state_test.dart
+++ b/examples/flutter_firebase_login/test/login/cubit/login_state_test.dart
@@ -1,41 +1,27 @@
 // ignore_for_file: prefer_const_constructors
 import 'package:flutter_firebase_login/login/login.dart';
 import 'package:flutter_test/flutter_test.dart';
-import 'package:form_inputs/form_inputs.dart';
-import 'package:formz/formz.dart';
 
 void main() {
-  const email = Email.dirty('email');
-  const password = Password.dirty('password');
+  const email = 'test@gmail.com';
+  const password = 'Test1234';
 
   group('LoginState', () {
     test('supports value comparisons', () {
       expect(LoginState(), LoginState());
     });
 
-    test('returns same object when no properties are passed', () {
-      expect(LoginState().copyWith(), LoginState());
-    });
-
-    test('returns object with updated status when status is passed', () {
-      expect(
-        LoginState().copyWith(status: FormzSubmissionStatus.initial),
-        LoginState(),
-      );
-    });
-
-    test('returns object with updated email when email is passed', () {
-      expect(
-        LoginState().copyWith(email: email),
-        LoginState(email: email),
-      );
-    });
+    group('isValid', () {
+      test('is false for initial state', () {
+        expect(LoginState().isValid, isFalse);
+      });
 
-    test('returns object with updated password when password is passed', () {
-      expect(
-        LoginState().copyWith(password: password),
-        LoginState(password: password),
-      );
+      test('is true when validation succeeds', () {
+        expect(
+          LoginState().withEmail(email).withPassword(password).isValid,
+          isTrue,
+        );
+      });
     });
   });
 }
diff --git a/examples/flutter_firebase_login/test/login/view/login_form_test.dart b/examples/flutter_firebase_login/test/login/view/login_form_test.dart
index 163f8ee1a9c..09b5eadea94 100644
--- a/examples/flutter_firebase_login/test/login/view/login_form_test.dart
+++ b/examples/flutter_firebase_login/test/login/view/login_form_test.dart
@@ -5,8 +5,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_firebase_login/login/login.dart';
 import 'package:flutter_firebase_login/sign_up/sign_up.dart';
 import 'package:flutter_test/flutter_test.dart';
-import 'package:form_inputs/form_inputs.dart';
-import 'package:formz/formz.dart';
 import 'package:mocktail/mocktail.dart';
 
 class MockAuthenticationRepository extends Mock
@@ -14,10 +12,6 @@ class MockAuthenticationRepository extends Mock
 
 class MockLoginCubit extends MockCubit<LoginState> implements LoginCubit {}
 
-class MockEmail extends Mock implements Email {}
-
-class MockPassword extends Mock implements Password {}
-
 void main() {
   const loginButtonKey = Key('loginForm_continue_raisedButton');
   const signInWithGoogleButtonKey = Key('loginForm_googleLogin_raisedButton');
@@ -25,8 +19,10 @@ void main() {
   const passwordInputKey = Key('loginForm_passwordInput_textField');
   const createAccountButtonKey = Key('loginForm_createAccount_flatButton');
 
-  const testEmail = 'test@gmail.com';
-  const testPassword = 'testP@ssw0rd1';
+  const validEmail = 'test@gmail.com';
+  const validPassword = 'Test1234';
+  const invalidEmail = 'invalid';
+  const invalidPassword = 'invalid';
 
   group('LoginForm', () {
     late LoginCubit loginCubit;
@@ -50,8 +46,8 @@ void main() {
             ),
           ),
         );
-        await tester.enterText(find.byKey(emailInputKey), testEmail);
-        verify(() => loginCubit.emailChanged(testEmail)).called(1);
+        await tester.enterText(find.byKey(emailInputKey), validEmail);
+        verify(() => loginCubit.emailChanged(validEmail)).called(1);
       });
 
       testWidgets('passwordChanged when password changes', (tester) async {
@@ -65,14 +61,14 @@ void main() {
             ),
           ),
         );
-        await tester.enterText(find.byKey(passwordInputKey), testPassword);
-        verify(() => loginCubit.passwordChanged(testPassword)).called(1);
+        await tester.enterText(find.byKey(passwordInputKey), validPassword);
+        verify(() => loginCubit.passwordChanged(validPassword)).called(1);
       });
 
       testWidgets('logInWithCredentials when login button is pressed',
           (tester) async {
         when(() => loginCubit.state).thenReturn(
-          const LoginState(isValid: true),
+          const LoginState().withEmail(validEmail).withPassword(validPassword),
         );
         await tester.pumpWidget(
           MaterialApp(
@@ -110,9 +106,9 @@ void main() {
           (tester) async {
         whenListen(
           loginCubit,
-          Stream.fromIterable(const <LoginState>[
-            LoginState(status: FormzSubmissionStatus.inProgress),
-            LoginState(status: FormzSubmissionStatus.failure),
+          Stream.fromIterable(<LoginState>[
+            const LoginState().withSubmissionInProgress(),
+            const LoginState().withSubmissionFailure(),
           ]),
         );
         await tester.pumpWidget(
@@ -131,9 +127,9 @@ void main() {
 
       testWidgets('invalid email error text when email is invalid',
           (tester) async {
-        final email = MockEmail();
-        when(() => email.displayError).thenReturn(EmailValidationError.invalid);
-        when(() => loginCubit.state).thenReturn(LoginState(email: email));
+        when(() => loginCubit.state).thenReturn(
+          const LoginState().withEmail(invalidEmail),
+        );
         await tester.pumpWidget(
           MaterialApp(
             home: Scaffold(
@@ -149,11 +145,9 @@ void main() {
 
       testWidgets('invalid password error text when password is invalid',
           (tester) async {
-        final password = MockPassword();
-        when(
-          () => password.displayError,
-        ).thenReturn(PasswordValidationError.invalid);
-        when(() => loginCubit.state).thenReturn(LoginState(password: password));
+        when(() => loginCubit.state).thenReturn(
+          const LoginState().withPassword(invalidPassword),
+        );
         await tester.pumpWidget(
           MaterialApp(
             home: Scaffold(
@@ -189,7 +183,7 @@ void main() {
       testWidgets('enabled login button when status is validated',
           (tester) async {
         when(() => loginCubit.state).thenReturn(
-          const LoginState(isValid: true),
+          const LoginState().withEmail(validEmail).withPassword(validPassword),
         );
         await tester.pumpWidget(
           MaterialApp(
diff --git a/examples/flutter_firebase_login/test/sign_up/cubit/sign_up_cubit_test.dart b/examples/flutter_firebase_login/test/sign_up/cubit/sign_up_cubit_test.dart
index bfc12e1fe03..385cf2c7a80 100644
--- a/examples/flutter_firebase_login/test/sign_up/cubit/sign_up_cubit_test.dart
+++ b/examples/flutter_firebase_login/test/sign_up/cubit/sign_up_cubit_test.dart
@@ -3,37 +3,18 @@ import 'package:authentication_repository/authentication_repository.dart';
 import 'package:bloc_test/bloc_test.dart';
 import 'package:flutter_firebase_login/sign_up/sign_up.dart';
 import 'package:flutter_test/flutter_test.dart';
-import 'package:form_inputs/form_inputs.dart';
-import 'package:formz/formz.dart';
 import 'package:mocktail/mocktail.dart';
 
 class MockAuthenticationRepository extends Mock
     implements AuthenticationRepository {}
 
 void main() {
-  const invalidEmailString = 'invalid';
-  const invalidEmail = Email.dirty(invalidEmailString);
-
-  const validEmailString = 'test@gmail.com';
-  const validEmail = Email.dirty(validEmailString);
-
-  const invalidPasswordString = 'invalid';
-  const invalidPassword = Password.dirty(invalidPasswordString);
-
-  const validPasswordString = 't0pS3cret1234';
-  const validPassword = Password.dirty(validPasswordString);
-
-  const invalidConfirmedPasswordString = 'invalid';
-  const invalidConfirmedPassword = ConfirmedPassword.dirty(
-    password: validPasswordString,
-    value: invalidConfirmedPasswordString,
-  );
-
-  const validConfirmedPasswordString = 't0pS3cret1234';
-  const validConfirmedPassword = ConfirmedPassword.dirty(
-    password: validPasswordString,
-    value: validConfirmedPasswordString,
-  );
+  const invalidEmail = 'invalid';
+  const validEmail = 'test@gmail.com';
+  const invalidPassword = 'invalid';
+  const validPassword = 't0pS3cret1234';
+  const invalidConfirmedPassword = 'invalid';
+  const validConfirmedPassword = 't0pS3cret1234';
 
   group('SignUpCubit', () {
     late AuthenticationRepository authenticationRepository;
@@ -49,32 +30,32 @@ void main() {
     });
 
     test('initial state is SignUpState', () {
-      expect(SignUpCubit(authenticationRepository).state, SignUpState());
+      expect(
+        SignUpCubit(authenticationRepository).state,
+        SignUpState(),
+      );
     });
 
     group('emailChanged', () {
       blocTest<SignUpCubit, SignUpState>(
         'emits [invalid] when email/password/confirmedPassword are invalid',
         build: () => SignUpCubit(authenticationRepository),
-        act: (cubit) => cubit.emailChanged(invalidEmailString),
-        expect: () => const <SignUpState>[SignUpState(email: invalidEmail)],
+        act: (cubit) => cubit.emailChanged(invalidEmail),
+        expect: () => <SignUpState>[SignUpState().withEmail(invalidEmail)],
       );
 
       blocTest<SignUpCubit, SignUpState>(
         'emits [valid] when email/password/confirmedPassword are valid',
         build: () => SignUpCubit(authenticationRepository),
-        seed: () => SignUpState(
-          password: validPassword,
-          confirmedPassword: validConfirmedPassword,
-        ),
-        act: (cubit) => cubit.emailChanged(validEmailString),
-        expect: () => const <SignUpState>[
-          SignUpState(
-            email: validEmail,
-            password: validPassword,
-            confirmedPassword: validConfirmedPassword,
-            isValid: true,
-          ),
+        seed: () => SignUpState()
+            .withPassword(validPassword)
+            .withConfirmedPassword(validConfirmedPassword),
+        act: (cubit) => cubit.emailChanged(validEmail),
+        expect: () => <SignUpState>[
+          SignUpState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withConfirmedPassword(validConfirmedPassword),
         ],
       );
     });
@@ -83,32 +64,24 @@ void main() {
       blocTest<SignUpCubit, SignUpState>(
         'emits [invalid] when email/password/confirmedPassword are invalid',
         build: () => SignUpCubit(authenticationRepository),
-        act: (cubit) => cubit.passwordChanged(invalidPasswordString),
-        expect: () => const <SignUpState>[
-          SignUpState(
-            confirmedPassword: ConfirmedPassword.dirty(
-              password: invalidPasswordString,
-            ),
-            password: invalidPassword,
-          ),
+        act: (cubit) => cubit.passwordChanged(invalidPassword),
+        expect: () => <SignUpState>[
+          SignUpState().withPassword(invalidPassword),
         ],
       );
 
       blocTest<SignUpCubit, SignUpState>(
         'emits [valid] when email/password/confirmedPassword are valid',
         build: () => SignUpCubit(authenticationRepository),
-        seed: () => SignUpState(
-          email: validEmail,
-          confirmedPassword: validConfirmedPassword,
-        ),
-        act: (cubit) => cubit.passwordChanged(validPasswordString),
-        expect: () => const <SignUpState>[
-          SignUpState(
-            email: validEmail,
-            password: validPassword,
-            confirmedPassword: validConfirmedPassword,
-            isValid: true,
-          ),
+        seed: () => SignUpState()
+            .withEmail(validEmail)
+            .withConfirmedPassword(validConfirmedPassword),
+        act: (cubit) => cubit.passwordChanged(validPassword),
+        expect: () => <SignUpState>[
+          SignUpState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withConfirmedPassword(validConfirmedPassword),
         ],
       );
 
@@ -116,23 +89,18 @@ void main() {
         'emits [valid] when confirmedPasswordChanged is called first and then '
         'passwordChanged is called',
         build: () => SignUpCubit(authenticationRepository),
-        seed: () => SignUpState(
-          email: validEmail,
-        ),
+        seed: () => SignUpState().withEmail(validEmail),
         act: (cubit) => cubit
-          ..confirmedPasswordChanged(validConfirmedPasswordString)
-          ..passwordChanged(validPasswordString),
-        expect: () => const <SignUpState>[
-          SignUpState(
-            email: validEmail,
-            confirmedPassword: validConfirmedPassword,
-          ),
-          SignUpState(
-            email: validEmail,
-            password: validPassword,
-            confirmedPassword: validConfirmedPassword,
-            isValid: true,
-          ),
+          ..confirmedPasswordChanged(validConfirmedPassword)
+          ..passwordChanged(validPassword),
+        expect: () => <SignUpState>[
+          SignUpState()
+              .withEmail(validEmail)
+              .withConfirmedPassword(validConfirmedPassword),
+          SignUpState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withConfirmedPassword(validConfirmedPassword),
         ],
       );
     });
@@ -142,27 +110,27 @@ void main() {
         'emits [invalid] when email/password/confirmedPassword are invalid',
         build: () => SignUpCubit(authenticationRepository),
         act: (cubit) {
-          cubit.confirmedPasswordChanged(invalidConfirmedPasswordString);
+          cubit.confirmedPasswordChanged(invalidConfirmedPassword);
         },
-        expect: () => const <SignUpState>[
-          SignUpState(confirmedPassword: invalidConfirmedPassword),
+        expect: () => <SignUpState>[
+          SignUpState().withConfirmedPassword(invalidConfirmedPassword),
         ],
       );
 
       blocTest<SignUpCubit, SignUpState>(
         'emits [valid] when email/password/confirmedPassword are valid',
         build: () => SignUpCubit(authenticationRepository),
-        seed: () => SignUpState(email: validEmail, password: validPassword),
-        act: (cubit) => cubit.confirmedPasswordChanged(
-          validConfirmedPasswordString,
-        ),
-        expect: () => const <SignUpState>[
-          SignUpState(
-            email: validEmail,
-            password: validPassword,
-            confirmedPassword: validConfirmedPassword,
-            isValid: true,
-          ),
+        seed: () {
+          return SignUpState()
+              .withEmail(validEmail)
+              .withPassword(validPassword);
+        },
+        act: (cubit) => cubit.confirmedPasswordChanged(validConfirmedPassword),
+        expect: () => <SignUpState>[
+          SignUpState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withConfirmedPassword(validConfirmedPassword),
         ],
       );
 
@@ -170,24 +138,16 @@ void main() {
         'emits [valid] when passwordChanged is called first and then '
         'confirmedPasswordChanged is called',
         build: () => SignUpCubit(authenticationRepository),
-        seed: () => SignUpState(email: validEmail),
+        seed: () => SignUpState().withEmail(validEmail),
         act: (cubit) => cubit
-          ..passwordChanged(validPasswordString)
-          ..confirmedPasswordChanged(validConfirmedPasswordString),
-        expect: () => const <SignUpState>[
-          SignUpState(
-            email: validEmail,
-            password: validPassword,
-            confirmedPassword: ConfirmedPassword.dirty(
-              password: validPasswordString,
-            ),
-          ),
-          SignUpState(
-            email: validEmail,
-            password: validPassword,
-            confirmedPassword: validConfirmedPassword,
-            isValid: true,
-          ),
+          ..passwordChanged(validPassword)
+          ..confirmedPasswordChanged(validConfirmedPassword),
+        expect: () => <SignUpState>[
+          SignUpState().withEmail(validEmail).withPassword(validPassword),
+          SignUpState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withConfirmedPassword(validConfirmedPassword),
         ],
       );
     });
@@ -203,18 +163,16 @@ void main() {
       blocTest<SignUpCubit, SignUpState>(
         'calls signUp with correct email/password/confirmedPassword',
         build: () => SignUpCubit(authenticationRepository),
-        seed: () => SignUpState(
-          email: validEmail,
-          password: validPassword,
-          confirmedPassword: validConfirmedPassword,
-          isValid: true,
-        ),
+        seed: () => SignUpState()
+            .withEmail(validEmail)
+            .withPassword(validPassword)
+            .withConfirmedPassword(validConfirmedPassword),
         act: (cubit) => cubit.signUpFormSubmitted(),
         verify: (_) {
           verify(
             () => authenticationRepository.signUp(
-              email: validEmailString,
-              password: validPasswordString,
+              email: validEmail,
+              password: validPassword,
             ),
           ).called(1);
         },
@@ -224,28 +182,22 @@ void main() {
         'emits [inProgress, success] '
         'when signUp succeeds',
         build: () => SignUpCubit(authenticationRepository),
-        seed: () => SignUpState(
-          email: validEmail,
-          password: validPassword,
-          confirmedPassword: validConfirmedPassword,
-          isValid: true,
-        ),
+        seed: () => SignUpState()
+            .withEmail(validEmail)
+            .withPassword(validPassword)
+            .withConfirmedPassword(validConfirmedPassword),
         act: (cubit) => cubit.signUpFormSubmitted(),
-        expect: () => const <SignUpState>[
-          SignUpState(
-            status: FormzSubmissionStatus.inProgress,
-            email: validEmail,
-            password: validPassword,
-            confirmedPassword: validConfirmedPassword,
-            isValid: true,
-          ),
-          SignUpState(
-            status: FormzSubmissionStatus.success,
-            email: validEmail,
-            password: validPassword,
-            confirmedPassword: validConfirmedPassword,
-            isValid: true,
-          ),
+        expect: () => <SignUpState>[
+          SignUpState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withConfirmedPassword(validConfirmedPassword)
+              .withSubmissionInProgress(),
+          SignUpState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withConfirmedPassword(validConfirmedPassword)
+              .withSubmissionSuccess(),
         ],
       );
 
@@ -261,29 +213,22 @@ void main() {
           ).thenThrow(SignUpWithEmailAndPasswordFailure('oops'));
         },
         build: () => SignUpCubit(authenticationRepository),
-        seed: () => SignUpState(
-          email: validEmail,
-          password: validPassword,
-          confirmedPassword: validConfirmedPassword,
-          isValid: true,
-        ),
+        seed: () => SignUpState()
+            .withEmail(validEmail)
+            .withPassword(validPassword)
+            .withConfirmedPassword(validConfirmedPassword),
         act: (cubit) => cubit.signUpFormSubmitted(),
-        expect: () => const <SignUpState>[
-          SignUpState(
-            status: FormzSubmissionStatus.inProgress,
-            email: validEmail,
-            password: validPassword,
-            confirmedPassword: validConfirmedPassword,
-            isValid: true,
-          ),
-          SignUpState(
-            status: FormzSubmissionStatus.failure,
-            errorMessage: 'oops',
-            email: validEmail,
-            password: validPassword,
-            confirmedPassword: validConfirmedPassword,
-            isValid: true,
-          ),
+        expect: () => <SignUpState>[
+          SignUpState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withConfirmedPassword(validConfirmedPassword)
+              .withSubmissionInProgress(),
+          SignUpState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withConfirmedPassword(validConfirmedPassword)
+              .withSubmissionFailure('oops'),
         ],
       );
 
@@ -299,28 +244,22 @@ void main() {
           ).thenThrow(Exception('oops'));
         },
         build: () => SignUpCubit(authenticationRepository),
-        seed: () => SignUpState(
-          email: validEmail,
-          password: validPassword,
-          confirmedPassword: validConfirmedPassword,
-          isValid: true,
-        ),
+        seed: () => SignUpState()
+            .withEmail(validEmail)
+            .withPassword(validPassword)
+            .withConfirmedPassword(validConfirmedPassword),
         act: (cubit) => cubit.signUpFormSubmitted(),
-        expect: () => const <SignUpState>[
-          SignUpState(
-            status: FormzSubmissionStatus.inProgress,
-            email: validEmail,
-            password: validPassword,
-            confirmedPassword: validConfirmedPassword,
-            isValid: true,
-          ),
-          SignUpState(
-            status: FormzSubmissionStatus.failure,
-            email: validEmail,
-            password: validPassword,
-            confirmedPassword: validConfirmedPassword,
-            isValid: true,
-          ),
+        expect: () => <SignUpState>[
+          SignUpState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withConfirmedPassword(validConfirmedPassword)
+              .withSubmissionInProgress(),
+          SignUpState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withConfirmedPassword(validConfirmedPassword)
+              .withSubmissionFailure(),
         ],
       );
     });
diff --git a/examples/flutter_firebase_login/test/sign_up/cubit/sign_up_state_test.dart b/examples/flutter_firebase_login/test/sign_up/cubit/sign_up_state_test.dart
index 39c36782dc6..603818d65dc 100644
--- a/examples/flutter_firebase_login/test/sign_up/cubit/sign_up_state_test.dart
+++ b/examples/flutter_firebase_login/test/sign_up/cubit/sign_up_state_test.dart
@@ -1,55 +1,31 @@
 // ignore_for_file: prefer_const_constructors
 import 'package:flutter_firebase_login/sign_up/sign_up.dart';
 import 'package:flutter_test/flutter_test.dart';
-import 'package:form_inputs/form_inputs.dart';
-import 'package:formz/formz.dart';
 
 void main() {
-  const email = Email.dirty('email');
-  const passwordString = 'password';
-  const password = Password.dirty(passwordString);
-  const confirmedPassword = ConfirmedPassword.dirty(
-    password: passwordString,
-    value: passwordString,
-  );
+  const email = 'test@gmail.com';
+  const password = 'Test1234';
 
   group('SignUpState', () {
     test('supports value comparisons', () {
       expect(SignUpState(), SignUpState());
     });
 
-    test('returns same object when no properties are passed', () {
-      expect(SignUpState().copyWith(), SignUpState());
-    });
-
-    test('returns object with updated status when status is passed', () {
-      expect(
-        SignUpState().copyWith(status: FormzSubmissionStatus.initial),
-        SignUpState(),
-      );
-    });
-
-    test('returns object with updated email when email is passed', () {
-      expect(
-        SignUpState().copyWith(email: email),
-        SignUpState(email: email),
-      );
-    });
-
-    test('returns object with updated password when password is passed', () {
-      expect(
-        SignUpState().copyWith(password: password),
-        SignUpState(password: password),
-      );
-    });
+    group('isValid', () {
+      test('is false for initial state', () {
+        expect(SignUpState().isValid, isFalse);
+      });
 
-    test(
-        'returns object with updated confirmedPassword'
-        ' when confirmedPassword is passed', () {
-      expect(
-        SignUpState().copyWith(confirmedPassword: confirmedPassword),
-        SignUpState(confirmedPassword: confirmedPassword),
-      );
+      test('is true when validation succeeds', () {
+        expect(
+          SignUpState()
+              .withEmail(email)
+              .withPassword(password)
+              .withConfirmedPassword(password)
+              .isValid,
+          isTrue,
+        );
+      });
     });
   });
 }
diff --git a/examples/flutter_firebase_login/test/sign_up/view/sign_up_form_test.dart b/examples/flutter_firebase_login/test/sign_up/view/sign_up_form_test.dart
index 1fdd473d28d..4d396517a70 100644
--- a/examples/flutter_firebase_login/test/sign_up/view/sign_up_form_test.dart
+++ b/examples/flutter_firebase_login/test/sign_up/view/sign_up_form_test.dart
@@ -3,18 +3,10 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_firebase_login/sign_up/sign_up.dart';
 import 'package:flutter_test/flutter_test.dart';
-import 'package:form_inputs/form_inputs.dart';
-import 'package:formz/formz.dart';
 import 'package:mocktail/mocktail.dart';
 
 class MockSignUpCubit extends MockCubit<SignUpState> implements SignUpCubit {}
 
-class MockEmail extends Mock implements Email {}
-
-class MockPassword extends Mock implements Password {}
-
-class MockConfirmedPassword extends Mock implements ConfirmedPassword {}
-
 void main() {
   const signUpButtonKey = Key('signUpForm_continue_raisedButton');
   const emailInputKey = Key('signUpForm_emailInput_textField');
@@ -22,9 +14,10 @@ void main() {
   const confirmedPasswordInputKey =
       Key('signUpForm_confirmedPasswordInput_textField');
 
-  const testEmail = 'test@gmail.com';
-  const testPassword = 'testP@ssw0rd1';
-  const testConfirmedPassword = 'testP@ssw0rd1';
+  const validEmail = 'test@gmail.com';
+  const validPassword = 'Test1234';
+  const invalidEmail = 'invalid';
+  const invalidPassword = 'invalid';
 
   group('SignUpForm', () {
     late SignUpCubit signUpCubit;
@@ -47,8 +40,8 @@ void main() {
             ),
           ),
         );
-        await tester.enterText(find.byKey(emailInputKey), testEmail);
-        verify(() => signUpCubit.emailChanged(testEmail)).called(1);
+        await tester.enterText(find.byKey(emailInputKey), validEmail);
+        verify(() => signUpCubit.emailChanged(validEmail)).called(1);
       });
 
       testWidgets('passwordChanged when password changes', (tester) async {
@@ -62,8 +55,8 @@ void main() {
             ),
           ),
         );
-        await tester.enterText(find.byKey(passwordInputKey), testPassword);
-        verify(() => signUpCubit.passwordChanged(testPassword)).called(1);
+        await tester.enterText(find.byKey(passwordInputKey), validPassword);
+        verify(() => signUpCubit.passwordChanged(validPassword)).called(1);
       });
 
       testWidgets('confirmedPasswordChanged when confirmedPassword changes',
@@ -80,17 +73,20 @@ void main() {
         );
         await tester.enterText(
           find.byKey(confirmedPasswordInputKey),
-          testConfirmedPassword,
+          validPassword,
         );
         verify(
-          () => signUpCubit.confirmedPasswordChanged(testConfirmedPassword),
+          () => signUpCubit.confirmedPasswordChanged(validPassword),
         ).called(1);
       });
 
       testWidgets('signUpFormSubmitted when sign up button is pressed',
           (tester) async {
         when(() => signUpCubit.state).thenReturn(
-          const SignUpState(isValid: true),
+          const SignUpState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withConfirmedPassword(validPassword),
         );
         await tester.pumpWidget(
           MaterialApp(
@@ -112,9 +108,9 @@ void main() {
           (tester) async {
         whenListen(
           signUpCubit,
-          Stream.fromIterable(const <SignUpState>[
-            SignUpState(status: FormzSubmissionStatus.inProgress),
-            SignUpState(status: FormzSubmissionStatus.failure),
+          Stream.fromIterable(<SignUpState>[
+            const SignUpState(),
+            const SignUpState().withSubmissionFailure(),
           ]),
         );
         await tester.pumpWidget(
@@ -133,9 +129,12 @@ void main() {
 
       testWidgets('invalid email error text when email is invalid',
           (tester) async {
-        final email = MockEmail();
-        when(() => email.displayError).thenReturn(EmailValidationError.invalid);
-        when(() => signUpCubit.state).thenReturn(SignUpState(email: email));
+        when(() => signUpCubit.state).thenReturn(
+          const SignUpState()
+              .withEmail(invalidEmail)
+              .withPassword(validPassword)
+              .withConfirmedPassword(validPassword),
+        );
         await tester.pumpWidget(
           MaterialApp(
             home: Scaffold(
@@ -151,12 +150,11 @@ void main() {
 
       testWidgets('invalid password error text when password is invalid',
           (tester) async {
-        final password = MockPassword();
-        when(
-          () => password.displayError,
-        ).thenReturn(PasswordValidationError.invalid);
-        when(() => signUpCubit.state)
-            .thenReturn(SignUpState(password: password));
+        when(() => signUpCubit.state).thenReturn(
+          const SignUpState()
+              .withEmail(validEmail)
+              .withPassword(invalidPassword),
+        );
         await tester.pumpWidget(
           MaterialApp(
             home: Scaffold(
@@ -173,12 +171,9 @@ void main() {
       testWidgets(
           'invalid confirmedPassword error text'
           ' when confirmedPassword is invalid', (tester) async {
-        final confirmedPassword = MockConfirmedPassword();
-        when(
-          () => confirmedPassword.displayError,
-        ).thenReturn(ConfirmedPasswordValidationError.invalid);
-        when(() => signUpCubit.state)
-            .thenReturn(SignUpState(confirmedPassword: confirmedPassword));
+        when(() => signUpCubit.state).thenReturn(
+          const SignUpState().withConfirmedPassword(invalidPassword),
+        );
         await tester.pumpWidget(
           MaterialApp(
             home: Scaffold(
@@ -214,7 +209,10 @@ void main() {
       testWidgets('enabled sign up button when status is validated',
           (tester) async {
         when(() => signUpCubit.state).thenReturn(
-          const SignUpState(isValid: true),
+          const SignUpState()
+              .withEmail(validEmail)
+              .withPassword(validPassword)
+              .withConfirmedPassword(validPassword),
         );
         await tester.pumpWidget(
           MaterialApp(
@@ -238,9 +236,17 @@ void main() {
           (tester) async {
         whenListen(
           signUpCubit,
-          Stream.fromIterable(const <SignUpState>[
-            SignUpState(status: FormzSubmissionStatus.inProgress),
-            SignUpState(status: FormzSubmissionStatus.success),
+          Stream.fromIterable(<SignUpState>[
+            const SignUpState()
+                .withEmail(validEmail)
+                .withPassword(validPassword)
+                .withConfirmedPassword(validPassword)
+                .withSubmissionInProgress(),
+            const SignUpState()
+                .withEmail(validEmail)
+                .withPassword(validPassword)
+                .withConfirmedPassword(validPassword)
+                .withSubmissionSuccess(),
           ]),
         );
         await tester.pumpWidget(