Flutter Bloc Package
⚠️ This article may be out of date. Please view the official up-to-date documentation at bloclibrary.dev.
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.
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).
Transitions occur when an Event
is dispatched
after mapEventToState
has been called but before the bloc’s state has been updated. A Transition
consists of the currentState
, the event
which was dispatched, and the nextState
.
Now that we understand events and states we can take a look at the Bloc API.
Bloc API
mapEventToState is a method that must be implemented when a class extends Bloc
. The function takes the incoming event as an argument. mapEventToState
is called whenever an event is dispatched
by the presentation layer. mapEventToState
must convert that event 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.
onTransition is a method that can be overridden to handle whenever a Transition
occurs. A Transition
occurs when a new Event
is dispatched and mapEventToState
is called. onTransition
is called before a bloc’s state has been updated. It is a great place to add bloc-specific logging/analytics.
Let’s create a counter bloc!
In order to create a Bloc, all we need to do is:
- Define our events and states
- Extend Bloc
- Override
initialState
andmapEventToState
.
In this case our events are CounterEvents
and our states are integers
.
Our CounterBloc
converts CounterEvents
to integers.
We can notify out CounterBloc
of events by calling dispatch
like so:
In order to observe state changes (Transitions
) we can override onTransition
.
Now every time we dispatch a CounterEvent
our Bloc will respond with a new integer
state and we will see a transition logged to the console.
Now let’s build a UI using Flutter and hook up the presentation layer to our CounterBloc
using the flutter_bloc package.
The flutter_bloc package provides two widgets to make interacting with Blocs easy:
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.
BlocProvider
BlocProvider
is a Flutter widget which provides a bloc to its children via BlocProvider.of(context)
. It is used as a dependency injection (DI) widget so that a single instance of a bloc can be provided to multiple widgets within a subtree.
Now let’s build our Counter App!
Our App
widget is a StatefulWidget
which is responsible for creating and disposing a CounterBloc
. It makes the CounterBloc
available to the CounterPage
widget using the BlocProvider
widget we mentioned above.
Our CounterPage
widget is a StatelessWidget
which uses BlocBuilder
to rebuild the UI in response to state changes from our CounterBloc
.
At this point we have successfully separated our presentational layer from our business logic layer. Notice that the CounterPage
widget knows nothing about what happens when a user taps the buttons. The widget simply tells the CounterBloc
that the user has pressed either the increment or decrement button.
That’s all there is to it!
For more examples and detailed documentation check out the official bloc documentation.
If you like the bloc library, you can support me by ⭐️the repository, or 👏 for this story.