19  comments

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.

This post is a part of a series. See all the parts 👉 here 👈

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.

About the author 

Matt Rešetár

Matt is an app developer with a knack for teaching others. Working as a Flutter freelancer and most importantly developer educator, he doesn't have a lot of free time 😅 Yet he still manages to squeeze in tough workouts 💪 and guitar 🎸

You may also like

Flutter Custom & Staggered Page Transition Animation Tutorial

Dio Connectivity Retry Interceptor – Flutter Tutorial

  • Hi Mark,
    Great job, thank you for all the work your are putting in these tutorials they are really really well presented and provide deep knowledge of how to develop professional and well architected App. I am following the series related to “Flutter Firebase” and I was wondering when are the next tutorials going to be published I can image it take a lot of work but I am can not wait for it.

      • Hi Matt, a pleasure to greet you. First of all, thank you for all the content that echo has and congratulate you for the classes that you share with us. In addition to this, I would like to know how I can buy the complete Flutter and firebase course, or will each of the videos just come out? in how much time?

  • Hi!
    First of all thank you for your amazing tutorials.

    I have a question about clean architecture, i would like to use retrofit in order to avoid writing http calls. But i dont really know where can i put my interface file ? Domain layer? Infrastructure layer?

    Thank you in advance 🙂

  • Hi Matt, thanks for working on this series at such a fast pace. Great stuff!

    I was wondering if you could provide the snippet for
    bloceventhandler? That would be super helpful.

    Thanks 😀

    • Hello Jason,

      Here it is :

      “Bloc Event”: {
      “prefix”: “blocevent”,
      “body”: [
      “(e) async* {${1}}”
      ],
      “description”: “Bloc Event”
      }

  • I have question:
    About using freezed and Either is it better than using rxdart (stream /controller/observer) ?

  • Matt, awesome! Thanks for your generosity in sharing your knowledge and inspiring a next generation of developers.
    I would like to know if you have touched about the ideal flutter development practices, consisting in how to use Git effectively, consistent and clear commit messages (conventional commits), planning/breaking the project into issues, branching and merging. Also how to integrate automated tests to the git repository, tools like Travis CI, code linting, code test coverage and automated detection of packages upgrade/testing.

    Thanks
    Roberto

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >