30  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 freelancer and most importantly developer educator, he is set on helping other people succeed in their Flutter app development career.

You may also like

  • 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!
    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 🙂

    • Hey! Retrofit or chopper interfaces should go into the infrastructure layer. That’s because they should be used from a repository, unless you’re 100% sure that you don’t want to have any data-fetching logic such as filtering or caching.

  • 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

  • Hi Matt,

    Just starting this course; can’t seem to find any tutorials after number 5. Is this course finished?

    Many thanks,

    George

  • HI, one question about the streams, as you are using StatelessWidgets, how are closing the streams to avoid memory leak, and in firebase case, reaching the Perrmission denied error?
    Thanks

  • For someone who uses provider/riverprod this was a bit confusing.
    In your architecture diagram you mentioned to use ViewModels and UseCases when one does not want to use BLoC.
    I struggle to implement it using riverprod (or provider). Could you show an example?

  • hi, matt,
    I am facing some dart error I think or because the version is different, in the sign_in_form_bloc.dart were the :

    SignInFormBloc(this._authFacade);

    the error is :

    The superclass ‘Bloc’ doesn’t have a zero argument constructor.
    Try declaring a zero argument constructor in ‘Bloc’, or explicitly invoking a different constructor in ‘Bloc’

    and if I press ctrl +. I got many fixes but don’t know which one to press.
    may you help me to continue with these lessons and thanks a lot I am very comfortable with your courses !

    • You need to pass in the initial state to the super constructor. SignInFormBloc(this._authFacade) : super(SignInFormState.initial());

  • Hello , I just saw that you called a method on the IAuthFacade , for example:
    _authFacade.getSignedInUser()
    But i don’t understand how it is possible since _authFacade is a IAuthFacade (an abstract class). I thought that abstract classes were just contracts for the classes that will implement them. So how is it possible to use _authFacade.getSignedInUser() ? Furthermore, the method getSignedInUser() inside IAuthFacade doesn’t even have a body, since it is abstract.
    Can you please explain that to me or refer me to some links that explain that point ? Thank you very much.

    • Hello Sina,

      As I can see in the part 4, the _authFacade is injected. It means, some class (not abstract anymore) implemented it, which was then injected into SingInFormBloc.

      class SignInFormBloc extends Bloc {
      final IAuthFacade _authFacade;

      SignInFormBloc(this._authFacade);

      BR,
      Viktor

  • The DDD course is helpful to me. I followed all of 5 episodes, the result of my code is clear and understandable. Thank you so much for your sharing it. I’m eager to wait for the next episodes you will to post. It’s specially on Infrastructure with local database and APIs, and also Presentation with BLOCs.

  • Hello, No Doubt this has been a most useful series, but can you please make few updates in article as bloc has been updated to version 8 and most of the implementations are old in this series.
    If you update article please ping me on my email.
    Thank you.

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