BLoC 💪 + flutter ❤️
Tags: flutter, flutterdev, flutterbloc
Published: 2020-05-22 07:10:22

BLOC means __B__*usiness* __L__*ogic* __O__*f* __C__*omponents*.
In Simple, this is kind of pattern to isolate the business logic and UI to meet the below things
1) Fast development
2) Easily testable
3) Reusable

In simple way to understand bloc, It takes an input of events and emitting an output of states
*Events can be triggered mostly by UI like button tap, scrolling change, Text change etc..*
*States will be listened in UI to re-render the widgets*

Thanks to [Flutter Bloc package](https://pub.dev/packages/flutter_bloc) which comes to save us 🤩🤩.
I would recommend to go through the official documentation of [flutter_bloc](https://bloclibrary.dev/) to get more info 😇.

We will see a real time scenario of user registration with bloc pattern in flutter

Assume we have a registration form, where we are having fields like Username, Password, Confirm password along with it we have register button.
Our model class will look like as below,
####RegistrationModel
```dart 
import 'package:flutter/material.dart';

class RegistrationModel {
  final String userName;
  final String password;
  final String confirmPassword;
  final bool isValidForRegistration;

  factory RegistrationModel.empty() {
    return RegistrationModel("", "", "", false);
  }

  RegistrationModel(this.userName, this.password, this.confirmPassword, this.isValidForRegistration);

  RegistrationModel copyWith(
      {String userName, String password, String confirmPassword, @required isValid}) {
    return RegistrationModel(userName ?? this.userName,
        password ?? this.password, confirmPassword ?? this.confirmPassword, isValid);
  }
}

```
For the registration form, we may have following business logic
1) Username should be mandatory, length should be greater than 8 and it should not be available in the existing user repository.

2) Password should match 8 character which should contain 1 uppercase, 1 lowercase, 1 number and 1 symbol

3) Confirm password should match with the original password

If all the above condition satisfies, we will enable 👍 the button until then we will disable the register button.

For our scenario - we are going to create a *RegistrationBloc* to handle business logic, *RegistrationEvent* to feed input to the bloc & *RegistrationState* to emit states from Bloc to re-render the UI.

__*Creating a bloc is very simple, only thing you need to understand your needs upfront to coding.*__
The scenario for us is pretty clear, so we will jump into the code.

#RegistrationState 🤠
We will have base state called RegistrationState which will take RegistrationModel as a parameter and this will be inherited by below two states

1) Initial State
2) Registration Model Changed

In Initial state, we will emit our initial model (empty model)
In Registration Model Changed, we will emit our model which has current values

``` dart
part of 'registration_bloc.dart';

abstract class RegistrationState extends Equatable {
  final RegistrationModel model;
  const RegistrationState(this.model);
}

class RegistrationInitial extends RegistrationState {
  RegistrationInitial() : super(RegistrationModel.empty());

  @override
  List<Object> get props => [model];
}

class RegistrationModelChanged extends RegistrationState {
  final RegistrationModel model;
  RegistrationModelChanged(this.model) : super(model);

  @override
  List<Object> get props => [model];
}

```
#Registration Event 👻
As per our scenario, we need to listen 3 text field changes
So our events will be,
1) UsernameChanged
2) PasswordChanged
3) ConfirmPasswordChanged

```dart
part of 'registration_bloc.dart';

abstract class RegistrationEvent extends Equatable {
  const RegistrationEvent();
}

class UserNameChanged extends RegistrationEvent {
  final String userName;
  UserNameChanged(this.userName);
  @override
  List<Object> get props => [userName];
}

class PasswordChanged extends RegistrationEvent{
  final String password;
  PasswordChanged(this.password);

  @override
  List<Object> get props => [password];
}

class ConfirmPasswordChanged extends RegistrationEvent{
  final String confirmPassword;
  ConfirmPasswordChanged(this.confirmPassword);

  @override
  List<Object> get props => [confirmPassword];
}
```

#Registration bloc 🥴
Create a bloc class need three pre-requisites
1) Class should be inherited from Bloc<TEvent, TState> which is from flutter_bloc package
```dart
class RegistrationBloc extends Bloc<RegistrationEvent, RegistrationState>
```
2) Class should override InitialState
```dart
@override
  RegistrationState get initialState => RegistrationInitial();
```
2) Class should override mapEventToState
```dart
@override
  Stream<RegistrationState> mapEventToState(
    RegistrationEvent event,
  ) async* {}
```
mapEventToState is corresponds to listening the event and emitting the states
Most of our logic going to be defined here only

we are validating the model using the method IsValid
```dart

  Future<bool> isValid(
      String userName, String password, String confirmPassword) async {
    bool isValidUser = await _userRepository.isUserAvailable(userName); //username is available to create

    RegExp exp = new RegExp(r"^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])(?=.{8,})");

    bool isUserNameValid = userName.length >= 8; // user name should have more than or equal to 8 characters
    bool isValidPassword = exp.hasMatch(password); // password should have one small case, one upper case, one number and one symbol
    bool isConfirmPasswordMatched = password == confirmPassword; // confirm password should match with the above password

    return isValidUser &&
        isUserNameValid &&
        isValidPassword &&
        isConfirmPasswordMatched; // true if all the conditions are true, else false
  }
```
Wrapping up all of these,
```dart
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:bloc_tuto/registration/bloc/user_repository.dart';
import 'package:bloc_tuto/registration/model/registration_model.dart';
import 'package:equatable/equatable.dart';

part 'registration_event.dart';
part 'registration_state.dart';

class RegistrationBloc extends Bloc<RegistrationEvent, RegistrationState> {
  final UserRepository _userRepository;
  RegistrationBloc(this._userRepository);

  @override
  RegistrationState get initialState => RegistrationInitial();

  @override
  Stream<RegistrationState> mapEventToState(
    RegistrationEvent event,
  ) async* {
    if (event is UserNameChanged) {
      bool isValidModel = await isValid(
          event.userName, state.model.password, state.model.confirmPassword);
      yield RegistrationModelChanged(state.model
          .copyWith(userName: event.userName, isValid: isValidModel));
    }
    if (event is PasswordChanged) {
      bool isValidModel = await isValid(
          state.model.userName, event.password, state.model.confirmPassword);
      yield RegistrationModelChanged(state.model
          .copyWith(password: event.password, isValid: isValidModel));
    }
    if (event is ConfirmPasswordChanged) {
      bool isValidModel = await isValid(
          state.model.userName, state.model.password, event.confirmPassword);
      yield RegistrationModelChanged(state.model.copyWith(
          confirmPassword: event.confirmPassword, isValid: isValidModel));
    }
  }

  Future<bool> isValid(
      String userName, String password, String confirmPassword) async {
    bool isValidUser = await _userRepository.isUserAvailable(userName); //username is available to create

    RegExp exp = new RegExp(r"^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])(?=.{8,})");

    bool isUserNameValid = userName.length >= 8; // user name should have more than or equal to 8 characters
    bool isValidPassword = exp.hasMatch(password); // password should have one small case, one upper case, one number and one symbol
    bool isConfirmPasswordMatched = password == confirmPassword; // confirm password should match with the above password

    return isValidUser &&
        isUserNameValid &&
        isValidPassword &&
        isConfirmPasswordMatched; // true if all the conditions are true, else false
  }
}
```
#UI

We are going to use 3 Textfield widgets for Username, Password & Confirm Password and BlocBuilder widget which has RaisedButton as a child.

__*BlocBuilder is a widget available in flutter_bloc package, which rebuilds on each state transitions.*__

We will listen each of the text changed and adds an event to bloc, Bloc in turns emits the necessary state which will be listened by bloc builder
So UI is now entirely isolated from our business logic, It just need to react for each state.

```dart
import 'package:bloc_tuto/registration/bloc/registration_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class RegistrationPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bloc pattern'),
      ),
      body: Container(
        child: Column(
          children: <Widget>[
            buildTextField(
                'UserName',
                false,
                (val) =>
                    context.bloc<RegistrationBloc>().add(UserNameChanged(val))),
            buildTextField(
                'Password',
                true,
                (val) =>
                    context.bloc<RegistrationBloc>().add(PasswordChanged(val))),
            buildTextField(
                'Confirm Password',
                true,
                (val) => context
                    .bloc<RegistrationBloc>()
                    .add(ConfirmPasswordChanged(val))),
            BlocBuilder<RegistrationBloc, RegistrationState>(
              condition: (oldState, newState) =>
                  oldState.model.isValidForRegistration !=
                  newState.model.isValidForRegistration, // re-build only when isValid changed from oldstate.
              builder: (context, state) {
                return RaisedButton(
                  onPressed: state != null && state.model.isValidForRegistration
                      ? () {} // enable press call only when model is valid for registration
                      : null,
                  child: Text('Register'),
                );
              },
            )
          ],
        ),
      ),
    );
  }

  TextField buildTextField(
      String hintText, bool isObscure, ValueChanged<String> onChangedCallback) {
    return TextField(
      obscureText: isObscure,
      decoration: InputDecoration(hintText: hintText),
      onChanged: onChangedCallback,
    );
  }
}
```
As our business logic & UI was isolated each other, we can test each of them individually without affecting other.

Full Sample along with test cases see [GitHub](https://github.com/Prakash-Selvaraj-Ash/prakash.selvaraj-outlook.com/tree/master/bloc_tuto) 😇😇

Happy fluttering!! 🤩🤩