Events and states for the **sign-in form BLoC **are in place after the previous part. It’s time to finally write some logic in the application layer that will be responsible for gluing the UI presentation layer together with the other layers of the app.

Bloc Logic

The logic performed inside BLoCs is focused on transforming _incoming _events into states. For example, a raw String will come in from the UI and a validated EmailAddress will come out.

Throughout this course, you’ll see that none of the logic we write will be overwhelming. By separating everything into multiple layers and classes, we can write extremely focused code doing just one thing at a time. We could say that our classes have a high cohesion.

We have created 5 events which upon which we should perform some logic inside the mapEventToState method. Let’s utilize the power of unions which will make sure we don’t forget to handle any of them.

sign_in_form_bloc.dart

@override
Stream<SignInFormState> mapEventToState(
  SignInFormEvent event,
) async* {
  yield* event.map(
    emailChanged: (e) async* {},
    passwordChanged: (e) async* {},
    registerWithEmailAndPasswordPressed: (e) async* {},
    signInWithEmailAndPasswordPressed: (e) async* {},
    signInWithGooglePressed: (e) async* {},
  );
}

You don’t have to write any of this boilerplate event handler code yourself. If you’re using VS Code, just enable Dart: New Completion Placeholders in the settings and the named arguments will be filled-in automatically.

Field updates

The simplest events to implement are those which simply receive unvalidated raw data from the UI and transform it into validated ValueObjects.

sign_in_form_bloc.dart

emailChanged: (e) async* {
  yield state.copyWith(
    emailAddress: EmailAddress(e.emailStr),
    authFailureOrSuccessOption: none(),
  );
},
passwordChanged: (e) async* {
  yield state.copyWith(
    password: Password(e.passwordStr),
    authFailureOrSuccessOption: none(),
  );
},

We have to reset the authFailureOrSuccessOption field whenever we emit a new state. This field holds a “response” from the previous call to sign in/register using IAuthFacade. Surely, when the email address changes, it’s not correct to associate the old “auth response” with the updated email address.

Sign in with Google

We’re finally going to call a method on the IAuthFacade from this event handler. First, we’ll indicate that the form is in the process of being submitted and once the signInWithGoogle method had a chance to run, we’ll yield a state containing either a failure (AuthFailure) or success (Unit).

sign_in_form_bloc.dart

signInWithGooglePressed: (e) async* {
  yield state.copyWith(
    isSubmitting: true,
    authFailureOrSuccessOption: none(),
  );
  final failureOrSuccess = await _authFacade.signInWithGoogle();
  yield state.copyWith(
      isSubmitting: false,
      authFailureOrSuccessOption: some(failureOrSuccess));
},

Register & sign in with email and password

These last two event handlers contain the largest amount of code. It’s still simple though and the logic can be broken down into a couple of steps. Let’s focus on the registration at first.

  1. Check if the entered EmailAddress and Password are valid.
  2. If valid, register using IAuthFacade and yield Some<Right<Unit>> in the authFailureOrSuccessOption state field.
  3. If invalid, indicate to start showing error messages and keep the authFailureOrSuccessOption set to None.

We know that ValueObjects have a value property which is of type Either. Therefore, to check if the inputted email address is valid, we can simply call myEmailAddress.value.isRight(). Wouldn’t it be more expressive though to call myEmailAddress.isValid()?

Let’s create such a method in the ValueObject super class:

domain/core/value_objects.dart

@immutable
abstract class ValueObject<T> {
  const ValueObject();
  Either<ValueFailure<T>, T> get value;

  bool isValid() => value.isRight();

  ...
}

With this, we can go ahead and implement the sign in event handler.

sign_in_form_bloc.dart

registerWithEmailAndPasswordPressed: (e) async* {
  final isEmailValid = state.emailAddress.isValid();
  final isPasswordValid = state.password.isValid();

  if (isEmailValid && isPasswordValid) {
    yield state.copyWith(
      isSubmitting: true,
      authFailureOrSuccessOption: none(),
    );

    final failureOrSuccess =
        await _authFacade.registerWithEmailAndPassword(
      emailAddress: state.emailAddress,
      password: state.password,
    );

    yield state.copyWith(
      isSubmitting: false,
      authFailureOrSuccessOption: some(failureOrSuccess),
    );
  }
  yield state.copyWith(
    showErrorMessages: true,
    authFailureOrSuccessOption: none(),
  );
},

In my opinion, we have way too many state.copyWith calls in the code above. We can simplify it a bit with the optionOf method, which turns null into none().
sign_in_form_bloc.dart

registerWithEmailAndPasswordPressed: (e) async* {
  Either<AuthFailure, Unit> failureOrSuccess;

  final isEmailValid = state.emailAddress.isValid();
  final isPasswordValid = state.password.isValid();

  if (isEmailValid && isPasswordValid) {
    yield state.copyWith(
      isSubmitting: true,
      authFailureOrSuccessOption: none(),
    );

    failureOrSuccess = await _authFacade.registerWithEmailAndPassword(
      emailAddress: state.emailAddress,
      password: state.password,
    );
  }
  yield state.copyWith(
    isSubmitting: false,
    showErrorMessages: true,
    // optionOf is equivalent to:
    // failureOrSuccess == null ? none() : some(failureOrSuccess)
    authFailureOrSuccessOption: optionOf(failureOrSuccess),
  );
},

Awesome! We can now implement the signInWithEmailAndPasswordPressed event handler but it will be suspiciously simple 🧐
sign_in_form_bloc.dart

signInWithEmailAndPasswordPressed: (e) async* {
  Either<AuthFailure, Unit> failureOrSuccess;

  final isEmailValid = state.emailAddress.isValid();
  final isPasswordValid = state.password.isValid();

  if (isEmailValid && isPasswordValid) {
    yield state.copyWith(
      isSubmitting: true,
      authFailureOrSuccessOption: none(),
    );

    failureOrSuccess = await _authFacade.signInWithEmailAndPassword(
      emailAddress: state.emailAddress,
      password: state.password,
    );
  }
  yield state.copyWith(
    isSubmitting: false,
    showErrorMessages: true,
    authFailureOrSuccessOption: optionOf(failureOrSuccess),
  );
},

Only one change! As is usually the case, this means only one thing - refactoring.

Refactoring

The only thing that changed between the** sign in** and **register **event handlers was the method which is being called on the IAuthFacade. This is a great opportunity to utilize the fact that Dart supports higher-order functions (a function which takes in another function as an argument).

We can break out the common code into a private method on the SignInFormBloc and then invoke a Function parameter which adheres to the signature of the IAuthFacade methods. In our case, the method has to return Future<Either<AuthFailure, Unit>> and take in an EmailAddress and a Password.

sign_in_form_bloc.dart

Stream<SignInFormState> _performActionOnAuthFacadeWithEmailAndPassword(
  Future<Either<AuthFailure, Unit>> Function({
    @required EmailAddress emailAddress,
    @required Password password,
  }) forwardedCall,
) async* {
  Either<AuthFailure, Unit> failureOrSuccess;

  final isEmailValid = state.emailAddress.isValid();
  final isPasswordValid = state.password.isValid();

  if (isEmailValid && isPasswordValid) {
    yield state.copyWith(
      isSubmitting: true,
      authFailureOrSuccessOption: none(),
    );

    failureOrSuccess = await forwardedCall(
      emailAddress: state.emailAddress,
      password: state.password,
    );
  }
  yield state.copyWith(
    isSubmitting: false,
    showErrorMessages: true,
    authFailureOrSuccessOption: optionOf(failureOrSuccess),
  );
}

Boom! This common private method allows us to cut the length of our code in half. This is how the two event handlers now look like:

sign_in_form_bloc.dart

registerWithEmailAndPasswordPressed: (e) async* {
  yield* _performActionOnAuthFacadeWithEmailAndPassword(
    _authFacade.registerWithEmailAndPassword,
  );
},
signInWithEmailAndPasswordPressed: (e) async* {
  yield* _performActionOnAuthFacadeWithEmailAndPassword(
    _authFacade.signInWithEmailAndPassword,
  );
},

FirebaseAuth is coming!

By now, we have implemented the **domain **and application layer classes responsible for signing in or registering. In the next part, we’re going to set up FirebaseAuth and hop into the infrastructure layer to implement the IAuthFacade.

#flutter #mobile-apps

Flutter Firebase & DDD Course [5] – Sign-In Form Logic (Bloc)
4.55 GEEK