Mastering Flutter: BLoC pattern for Login: Part 1
Tags: beginners, flutter, dart, tutorial
Published: 2021-05-05 15:12:21

Handle a login / signup flow can cause some serious headaches with Flutter, using BLoC can make your life much easier and clarify the whole process, so let's dive in this tuto... journey!

First things first, since this isn't a basic tutorial we will take for granted the knowledge of the **routes** and we've also included a little bit of "validation" with [formz ](https://pub.dev/packages/formz)package to create reusable models; it's not the purpose of this tutorial to show how this will work, you will see this in the next tutorial. For the login part we've also used, for tutorial purposes, a subset of BLoC (Cubit) so you will see the difference between those two.

Before we dive in let's add to our *pubspec.yaml* the necessary packages:

```markup
equatable: ^2.0.0
flutter_bloc: ^7.0.0
formz: ^0.3.2
```

Adding the *equatable* package will only make your life easier but if you want to compare instances of classes "manually" you just need to override "==" and the hashCode. 

## Login State

Let's start with a class that will contain the status of the form and all the fields states:

```dart
class LoginState extends Equatable {
  const LoginState({
    this.email = const Email.pure(),
    this.password = const Password.pure(),
    this.status = FormzStatus.pure,
    this.exceptionError,
  });

  final Email email;
  final Password password;
  final FormzStatus status;
  final String exceptionError;

  @override
  List<Object> get props => [email, password, status, exceptionError];

  LoginState copyWith({
    Email email,
    Password password,
    FormzStatus status,
    String error,
  }) {
    return LoginState(
      email: email ?? this.email,
      password: password ?? this.password,
      status: status ?? this.status,
      exceptionError: error ?? this.exceptionError,
    );
  }
}
```

Now let's create our **LoginCubit,** this will be responsible to perform logic such as getting the email and output new states through emit:

```dart
class LoginCubit extends Cubit<LoginState> {
  LoginCubit() : super(const LoginState());

  void emailChanged(String value) {
    final email = Email.dirty(value);
    emit(state.copyWith(
      email: email,
      status: Formz.validate([
        email,
        state.password
      ]),
    ));
  }

  void passwordChanged(String value) {
    final password = Password.dirty(value);
    emit(state.copyWith(
      password: password,
      status: Formz.validate([
        state.email,
        password
      ]),
    ));
  }

  Future<void> logInWithCredentials() async {
    if (!state.status.isValidated) return;
    emit(state.copyWith(status: FormzStatus.submissionInProgress));
    try {
      await Future.delayed(Duration(milliseconds: 500));
      emit(state.copyWith(status: FormzStatus.submissionSuccess));
    } on Exception catch (e) {
      emit(state.copyWith(status: FormzStatus.submissionFailure, error: e.toString()));
    }
  }
}
```
 

But how can we connect the Cubit to our UI? Here that comes to the rescue the *BlocProvider,* a widget which provides a bloc to its children using: *BlocProvider*.of<LoginCubit>(context) 

```dart
BlocProvider(
  create: (_) => LoginCubit(),
  child: LoginForm(),
),
```


## Login Form

Since now seems all at his own place it's time to settle down the last piece of our puzzle, the whole UI

```dart
class LoginForm extends StatelessWidget {
  const LoginForm({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocConsumer<LoginCubit, LoginState>(
        listener: (context, state) {
          if (state.status.isSubmissionFailure) {
            print('submission failure');
          } else if (state.status.isSubmissionSuccess) {
            print('success');
          }
        },
        builder: (context, state) => Stack(
          children: [
            Positioned.fill(
              child: SingleChildScrollView(
                padding: const EdgeInsets.fromLTRB(38.0, 0, 38.0, 8.0),
                child: Container(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    mainAxisAlignment: MainAxisAlignment.start,
                    children: [
                      _WelcomeText(),
                      _EmailInputField(),
                      _PasswordInputField(),
                      _LoginButton(),
                      _SignUpButton(),
                    ],
                  ),
                ),
              ),
            ),
            state.status.isSubmissionInProgress
                ? Positioned(
              child: Align(
                alignment: Alignment.center,
                child: CircularProgressIndicator(),
              ),
            ) : Container(),
          ],
        )
    );
  }
}
```

 
To react to the new states emitted by out Cubit we need to wrap our form in a **BlocConsumer;** now we'll have exposed a listener and a builder.

##### Listener

Here we will listen for state changes and, for example, show an error in response of an API call or perform Navigation.

##### Builder

Here we will show the ui reacting to state changes of our Cubit.

### UI

Our UI consists of a Column with 5 children but we'll just show 2 widgets to be brief:

```dart
class _EmailInputField extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<LoginCubit, LoginState>(
      buildWhen: (previous, current) => previous.email != current.email,
      builder: (context, state) {
        return AuthTextField(
          hint: 'Email',
          key: const Key('loginForm_emailInput_textField'),
          keyboardType: TextInputType.emailAddress,
          error: state.email.error.name,
          onChanged: (email) => context
              .read<LoginCubit>()
              .emailChanged(email),
        );
      },
    );
  }
}
```


```dart
class _LoginButton extends StatelessWidget {
  const _LoginButton({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<LoginCubit, LoginState>(
      buildWhen: (previous, current) => previous.status != current.status,
      builder: (context, state) {
        return CupertinoButton(
            child: Text('Login'),
            onPressed: state.status.isValidated
                ? () => context.read<LoginCubit>().logInWithCredentials()
                : null
        );
      },
    );
  }
}
```

Both widgets are wrapped in a **BlocBuilder** that's responsible to rebuild these widgets only when the cubit emits new states for their respective evaluated properties, so for example, if the user doesn't type anything in the email field, *_EmailInputField* won't ever be rebuilt.

The button instead, if all fields are validated, will invoke *logInWithCredentials()* function that will emit a new status (failure or success) based on the API response.

**Part 1** of our journey containing the login flow comes to an end, you can reach this tutorial's code inside this [GitHub](https://github.com/Alessandro-v/bloc_login) repository. See you the next time with the Sign Up Flow