Flutter Bloc Package

After having worked with Flutter for a bit, I decided to create a package to help with something that I have used quite frequently: the BLoC pattern.

For those not familiar with the BLoC pattern, it is a design pattern which helps separate the presentation layer from the business logic. You learn more about it here.

While using the BLoC pattern can prove to be challenging due to the setup as well as an understanding of Streams and Reactive Programming, at it’s core a BLoC is pretty simple:

A BLoC takes a stream of events as input and transforms them into a stream of states as output.

High-Level BLoC Architecture

We can now use this powerful design pattern with the help of the bloc package.


This package abstracts reactive aspects of the pattern allowing developers to focus on converting events into states.

Let’s start by defining those terms…

Glossary

Events are the input to a Bloc. They are commonly UI events such as button presses. Events are dispatched and converted to States.

States are the output of a Bloc. Presentation components can listen to the stream of states and redraw portions of themselves based on the given state (see BlocBuilder for more details).

Now that we understand events and states we can take a look at how to create a simple bloc.

Bloc Interface

mapEventToState is a method that must be implemented when a class extends Bloc. The function takes two arguments: state and event. mapEventToState is called whenever an event is dispatched by the presentation layer. mapEventToState must convert that event, along with the current state, into a new state and return the new state in the form of a Stream which is consumed by the presentation layer.

dispatch is a method that takes an event and triggers mapEventToState. dispatch may be called from the presentation layer or from within the Bloc (see examples) and notifies the Bloc of a new event.

initialState is the state before any events have been processed (before mapEventToState has ever been called). initialState is an optional getter. If unimplemented, initialState will be null.

transform is a method that can be overridden to transform the Stream<Event> before mapEventToState is called. This allows for operations like distinct() and debounce() to be used.

Let’s create a simple bloc!

Simple Bloc Example

In order to create a Bloc, all we need to do is override mapEventToState.

The Bloc above isn’t very interesting; it just returns a stream of static strings in response to any event. That isn’t a very realistic use-case so let’s look at something more practical like a login flow.


We’re going to need to define what our different LoginStates are going to be. For simplicity, let's say we only have 4 states:

  • initial
  • loading
  • failure
  • success
LoginState Sample

Next we need to define the different events that our Bloc will respond to. Again, for simplicity, let’s say there is just a single event we will handle: LoginButtonPressed.

LoginEvent Sample

Now that we’ve identified our states and events, our LoginBloc should look something like:

LoginBloc Sample

Notice that our LoginBloc provides an initialState and exposes a public method called onLoginButtonPressed so that the presentation layer can dispatch a LoginButtonPressed event.

Now that we have the LoginBloc lets take a look at how to use BlocBuilder to hook up our LoginForm widget to our LoginBloc… but first what is BlocBuilder?

BlocBuilder

BlocBuilder is a Flutter widget which requires a Bloc and a builder function. BlocBuilder handles building a widget in response to new states. BlocBuilder is very similar to StreamBuilder but has a more simple API to reduce the amount of boilerplate code needed.

Now that we know what BlocBuilder is, let’s see it in action!

LoginForm Sample

Awesome! We have successfully separated our presentational layer from our business logic layer.

Notice that the LoginForm widget knows nothing about what happens when a user taps the button. The form simply tells the LoginBloc that the user has pressed the button via dispatch.

From that point, the LoginBloc tells the LoginForm to be in the loading state and proceeds to authenticate the user. If the user is successfully authenticated, the LoginBloc tells the LoginForm to be in the LoginSuccess state. If authentication failed, the LoginBloc tells the LoginForm to be in the LoginError state.

That’s all there is to it!

You can find the package here and checkout more examples here.

If you like the bloc package, you can support me by ⭐️the repository, or 👏 for this story.