Traceability

One of the biggest advantages of using Bloc is knowing the sequence of state changes as well as exactly what triggered those changes. For state that is critical to the functionality of an application, it might be very beneficial to use a more event-driven approach in order to capture all events in addition to state changes.

A common use case might be managing AuthenticationState. For simplicity, let’s say we can represent AuthenticationState via an enum:

enum AuthenticationState { unknown, authenticated, unauthenticated }

There could be many reasons as to why the application’s state could change from authenticated to unauthenticated. For example, the user might have tapped a logout button and requested to be signed out of the application. On the other hand, maybe the user’s access token was revoked and they were forcefully logged out. When using Bloc we can clearly trace how the application state got to a certain state.

Transition {
  currentState: AuthenticationState.authenticated,
  event: LogoutRequested,
  nextState: AuthenticationState.unauthenticated
}

The above Transition gives us all the information we need to understand why the state changed. If we had used a Cubit to manage the AuthenticationState, our logs would look like:

Change {
  currentState: AuthenticationState.authenticated,
  nextState: AuthenticationState.unauthenticated
}

This tells us that the user was logged out but it doesn’t explain why which might be critical to debugging and understanding how the state of the application is changing over time.

Advanced Event Transformations

Another area in which Bloc excels over Cubit is when we need to take advantage of reactive operators such as bufferdebounceTimethrottle, etc.

Bloc has an event sink that allows us to control and transform the incoming flow of events.

For example, if we were building a real-time search, we would probably want to debounce the requests to the backend in order to avoid getting rate-limited as well as to cut down on cost/load on the backend.

With Bloc we can provide a custom EventTransformer to change the way incoming events are processed by the Bloc.

EventTransformer<T> debounce<T>(Duration duration) {
  return (events, mapper) => events.debounceTime(duration).flatMap(mapper);
}

CounterBloc() : super(0) {
  on<Increment>(
    (event, emit) => emit(state + 1),
    /// Apply the custom `EventTransformer` to the `EventHandler`.
    transformer: debounce(const Duration(milliseconds: 300)),
  );
}

With the above code, we can easily debounce the incoming events with very little additional code.

Check out package:bloc_concurrency for an opinionated set of event transformers.

If you are unsure about which to use, start with Cubit and you can later refactor or scale-up to a Bloc as needed.