6  comments

The IAuthFacade abstraction is in place after the previous part. Using this interface allows us to write sign-in form application logic without having a clue about how the authentication is actually implemented. Let's model how the UI will communicate with the BLoC using states and events.

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

Bloc overview

As shown on the diagram, a BLoC receives events from the UI which contain raw data and outputs states which contain validated data, among other things. Let's now create the classes for the SignInFormBloc.

If you're not yet familiar with the BLoC library check out this tutorial first.
To greatly cut down on the time spent creating files, use extensions for VS Code or IntelliJ.

Let's add flutter_bloc as a dependency.

pubspec.yaml

dependencies:
  ...
  flutter_bloc: ^3.2.0

Create new directories so that the path application/auth/sign_in_form exists. Using the extensions mentioned above, create a a Bloc with the name "sign_in_form".

Folder contents

It's important to remember that events and states are a part of the presentation layer. It is, therefore, fine if they're tightly coupled to what's going on in the Flutter widgets.

Events

What possible events can occur in the UI of the sign-in form? Apart from the different sign-in buttons being pressed, there are also two other events which are usually handled directly in the UI unless you're following DDD - email and password input changes. Expressed as a union, the events will look like:

sign_in_form_event.dart

part of 'sign_in_form_bloc.dart';

@freezed
abstract class SignInFormEvent with _$SignInFormEvent {
  // Notice that these events take in "raw" unvalidated Strings
  const factory SignInFormEvent.emailChanged(String emailStr) = EmailChanged;
  const factory SignInFormEvent.passwordChanged(String passwordStr) =
      PasswordChanged;
  const factory SignInFormEvent.registerWithEmailAndPasswordPressed() =
      RegisterWithEmailAndPasswordPressed;
  const factory SignInFormEvent.signInWithEmailAndPasswordPressed() =
      SignInWithEmailAndPasswordPressed;
  const factory SignInFormEvent.signInWithGooglePressed() =
      SignInWithGooglePressed;
}
We don't specify the usual part '*.freezed.dart' statement here because of the part of statement.

State

BLoC usually outputs multiple subclasses (or union cases) of state. This is different with BLoCs responsible for validating UI data which have only a single state class. 

What do we need to communicate back to the UI of the sign-in form?

1. Validated values

We surely want to pass back the validated EmailAddress and Password value objects to be able to show error messages in the TextFormFields.

2. Auth progress

Showing a progress indicator is a no-brainer, so we have to also pass back a bool isSubmitting.

3. Success or error backend response

To show an error Snackbar when something goes wrong in the backend, we will need to pass back the Either<AuthFailure, Unit> returned from the IAuthFacade. We're going to call it authFailureOrSuccess and you can think of it as of the auth backend "response".

However, there will initially be no response until the user presses a button. We could just initially assign null to the authFailureOrSuccess field but you know that this sucks.

A much better option would be to use an Option 🙃. Much like Either, it's a union of two values - Some and None. It's a sort of a non-nullable type where null gets replaced by the None union case. Only the Some union case holds a value which will be the Either<AuthFailure, Unit>.

4. Whether or not to show input error messages

Lastly, we want to show the input validation error messages under the TextFormFields only after the first press of a sign-in/register button. This will be communicated back to the UI inside a bool showErrorMessages.

sign_in_form_state.dart

part of 'sign_in_form_bloc.dart';

@freezed
abstract class SignInFormState with _$SignInFormState {
  const factory SignInFormState({
    @required EmailAddress emailAddress,
    @required Password password,
    @required bool showErrorMessages,
    @required bool isSubmitting,
    @required Option<Either<AuthFailure, Unit>> authFailureOrSuccessOption,
  }) = _SignInFormState;

  factory SignInFormState.initial() => SignInFormState(
        emailAddress: EmailAddress(''),
        password: Password(''),
        showErrorMessages: false,
        isSubmitting: false,
        authFailureOrSuccessOption: none(),
      );
}

Bloc

While events and the state data class can be viewed as View Models which live in the presentation layer, the SignInFormBloc class itself performs application logic, i.e. orchestrating the other layers to work together.

This is where the raw Strings turn into validated ValueObjects and where the IAuthFacade's methods are called. The logic performed here is focused on transforming incoming events into states.

File & class setup

Although we'll leave writing the logic for the next part, let's at least set the file up. Because of the changes we've done to the default events and states generated by the extension, we need to add the part statement for freezed and change the initialState. Additionally, we'll add a field for the IAuthFacade dependency.

sign_in_form_bloc.dart

part 'sign_in_form_event.dart';
part 'sign_in_form_state.dart';

part 'sign_in_form_bloc.freezed.dart';

class SignInFormBloc extends Bloc<SignInFormEvent, SignInFormState> {
  final IAuthFacade _authFacade;

  SignInFormBloc(this._authFacade);

  @override
  SignInFormState get initialState => SignInFormState.initial();

  @override
  Stream<SignInFormState> mapEventToState(
    SignInFormEvent event,
  ) async* {
    // TODO: Implement
  }
}

Now it's finally the time to run our most favorite command to kick off code generation.

👨‍💻 terminal

flutter pub run build_runner watch --delete-conflicting-outputs

Get ready for the logic

We took important steps in this part by modeling the events that can be performed by the UI  and the state which is a way for the BLoC to communicate values back to the UI. With all of this in place, we will be able to jump right into writing the application logic.

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 💪

You may also like

Flutter Bloc & Cubit Tutorial

Flutter Custom & Staggered Page Transition Animation Tutorial

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