The BLoC-Pattern

When it comes to professional software development one goal is to create maintainable and testable source code. To fulfill that goal one important step is to use an architecture which enables us to fulfill that goal.

A testable app ensures that it is easy to write good tests, with good/reasonable coverage. These tests then ensure that the app works as expected and that changes made to the codebase do not break the existing functionality.

In Flutter, the BLoC (Business Logic Component) pattern is a popular choice for building testable apps. It is a way to separate the business logic of an app from the user interface, making it easier to test the business logic in isolation.

Cubit is a variation of the BLoC pattern that makes it even simpler. It is a lightweight, easy-to-use package that allows developers to manage the state of their app in a simple and predictable way.

In this blog series and in the examples we are using the https://pub.dev/packages/flutter_bloc, which helps us to implement the BLoC pattern in our paperchase app which is being developed with Flutter.

This first article of the blog series will introduce the BLoC pattern and how it works.
In a second part we will show you how the pattern is used, how it increases the testability of our application, and how we can unit test our app code.

 

Architecture

 

Using the BLoC pattern allows us to separate our application into presentation layer, business logic layer, and data layer.
The presentation layer (UI) uses states to figure out how to render itself. Additionally, it is responsible for handling user input and lifecycle events.
While the data layer is responsible for managing our data the business logic responds to input from the presentation layer with new states.
The component which holds the state and parts of the app logic is our BLoC object which can be a Bloc or a Cubit.

 

Difference to MVVM?

 

Initially, BLoC and MVVM appeared distinct, but over time that difference faded away as BLoC implementations changed. Presently, the primary distinction lies in the fact that BLoC does not explicitly differentiate between presentation logic and business logic, or at least it does so in a less conspicuous manner.
In MVVM, the presentation logic is responsible for managing the interaction between UI elements and the application’s business logic. Conversely, in some BLoC implementations, presentation logic is integrated within the BLoCs, whereas in others, it is situated within the UI layer.
You can also express that BLoC pairs logic into functional sets while MVVM pairs logic by what the view requires.

 

Cubit

 

https://bloclibrary.dev/assets/cubit_architecture_full.png

 

A Cubit is basically the simpler version of a BLoC. It exposes functions which are used by the presentation layer to trigger state changes. In the listing below we can see what the Cubit looks like. When we create a Cubit, we must define the type of state to be managed by it.

class UserCubit extends Cubit {
  UserCubit() : super(UserState.initial());

  void setName(String name) {
    emit(state.copyWith(name: name));
  } 
  
  void increasePoints() {

    ...

    emit(state.copyWith(
      points: currentPoints,
      streak: streak,
      onFire: onFire,
      shouldShowOnFireDialog: shouldShowOnFireDialog,
    ));
  }
}

Of course you can use a Cubit with a simple state like a String or an Int. But to have a more realistic example we use a class instead of a primitive type.
In our example we created a UserState which looks like this:

@immutable
class UserState with EquatableMixin {
  final String name;
  final int points;

  UserState({
    required this.name,
    required this.points,
  });

  factory UserState.initial() => UserState(
      name: '',
      points: 0,
      );

  UserState copyWith({
    String? name,
    int? points,
  }) {
    return UserState(
      name: name ?? this.name,
      points: points ?? this.points,
    );
  }

We have an immutable state which has several attributes and implements the EquateableMixin.
The EquateableMixin helps us to implement equality without needing to explicitly override operator == and hashCode.
We also implemented a copyWith method, which allows us to easily change specific attributes of our state.
With the initial constructor we define an initial state we need to specify in our cubit.
The cubit has several methods, which can be called externally and output new states via emit.
In the cubit example, when the setName method is called, a new state with a new name is emitted.

 

Bloc

 

After looking into a Cubit, let us see what a Bloc is and what the differences between them are.

https://bloclibrary.dev/assets/bloc_architecture_full.png

 

In addition to the state that we are managing we also need to define the events that the Bloc can process.
Instead of methods we must register event handlers in our Bloc. These are responsible for converting incoming events into outgoing states.
Using the Bloc to trigger a state change means we add an event to it.

As the Bloc is event-driven, we are also able to capture information about what triggered the state change.
This can be done by overriding onTransition, which contains the event that triggered the change additional to the currentState and the nextState.
Another possibility you have when using Blocs is that you can override onEvent, which is called when a new event is registered to the Bloc.

 

Bloc or Cubit?

 

Now, when should I use a Cubit and when should I use a Bloc?

The simple answer is that you should use a Cubit whenever it is sufficient. It is easier to understand and we end up with less code we need to implement.
A Cubit has less complexity because it is a subset of a Bloc, which does not rely on events. Instead, it uses methods to emit new states.

However, if we want to have a better traceability (i.e., we want to know the sequence of state changes or what exactly triggered a state change) we can use a Bloc.
The need for an advanced event transformation (e.g., debouncing requests to the backend to avoid getting rate limits) can be another reason for using a Bloc.

After all, you can still start with a Cubit and – if necessary – easily exchange it with a Bloc. This is because the way that state changes are observed in the presentation layer is the same for both.
If you want to learn more about the BLoC pattern or if you are searching for a good reference guide, you can look at bloclibrary.dev.

In the second post, we will show you how to use these components and how we can unit test our application.

Michael Sandner
Letzte Artikel von Michael Sandner (Alle anzeigen)