Flutter BLoC, from the perspective of a beginner

The predictable state-management pattern explained.

Anurag Roy
7 min readMay 11, 2020

If you have a fairly good understanding of development in Flutter, chances are that you have already come across something called “state management”. While the importance of state management warrants its separate article, the meat and potatoes of this article are meant as an attempt to explain one of the most popular ways of managing state, the BLoC pattern.

We live in an asynchronous world. Every action, whether it is fetching data from a remote server, reading a file from the device or handling of user input in your Flutter app, is asynchronous. Programming synchronously will outright make your app halt and make its performance jank blow up in your face.

Aw, snap!

Fortunately, Dart, the language in which Flutter apps are written, was made keeping in mind this asynchronous nature. It comes with out-of-the-box support for asynchronous operations and streams. Considering that, it would be a nice idea where the user input handling could be implemented asynchronously too. That’s were Business Logic Components come in.

From someone who has not used the BLoC for the majority of his Flutter development until just recently, BLoC may be intimidating, considering its share of boilerplate and usage of streams. So before you hold your breath, let me switch the topic and introduce to you about a hypothetical state management solution which I call, say “state flow”.

TL;DR: If you are about watching rather than reading and want to see some more code along the way, here’s the video tutorial from Reso Coder that pretty much conveys the same thought(and inspired me to write this article).

Introducing: State flow

In a typical state management solution, what we usually look for is data driving UI, not the other way around.

So we have the data/repository, which makes the UI of our app build its appearance depending upon it. The UI also changes the data by invoking an action. The updated data, in turn, changes the appearance of the UI by invoking a rebuild, and this cycle is continued.

Of course, if your data needs to be fetched from a remote server, then the above diagram would slightly change:

But let’s just ignore that for this article.

void _increment(){ // Step 1: The UI invokes this action/function
setState( // Step 3: The widget is rebuilt
(){
counter++; // Step 2: Data gets changed
}
);
}

Taking into account the familiar setState(), we could say that invoking the _increment function, for instance, can be called as the action where the data is changed, and the setState function is, in turn, invoked to change the UI.

Now, look at the situation this way:

Continuing the hypothetical approach, let us define a generic Pipe class which takes this action function and passes it to “setStateLauncher”, and it, in turn, passes the setState function to the UI to have it rebuilt(in English, invokes setState).

Now rebuilding widgets is very cheap in Flutter, where any part of the widget tree can be swapped out for other widgets. We could take advantage of that, and define a StateFlowBuilder, which has a builder function that builds UI depending upon the state/data, which we pass from the setStateLauncher(now StateLauncher):

StateFlowBuilder(
builder: (context, state){
if(state.hasData){
//build UI
}else{
return CircularProgressIndicator();
}
}
)

By doing that, our state management diagram is a bit simplified:

Now, what’s inside this StateLauncher?

Let’s just say, that it is a simple function that maps(foreshadowing alert!) an action to the respective state since we can’t magically change an action object or function to a state object. Do note that, there can be different types of action coming from various sources in the app. So we need to ensure that the appropriate state is passed to the UI.

Action(Increment) => State(counter+1)

Action(Decrement) => State(counter-1)

Okay, so you can see that my state management solution is very easy to understand. The UI fires an action, the action passes through a pipe, updates the data and gets mapped to a state, and then is rebuilt upon by the UI. You can see that such an approach ensures that the state is changed only when we let the UI pass/invoke an action, which would make it…

a predictable state management library.

Sounds familiar?

Introducing: BLoC from scratch

Wait wait, please don’t panic and leave now. We are not there yet. First, let me give you a step by step reality checks about State flow:

  • The “Pipe” class is a Stream, one connecting UI to the “StateLauncher” and another connecting “StateLauncher” to UI.
  • The action passing through the first set of pipes is known as an Event, whereas passing through the second set of pipes is the State, containing the updated data.
  • The “StateLauncher” is a function known as mapEventToState.
  • The “StateFlowBuilder” is a StreamBuilder.

Let us see how the “State flow” diagram looks in reality:

Notice the Stream generics.

Let us go through a bit of code to let this “sink” in a bit.

Here we create a reference to the data object the UI will depend upon. Simple stuff, not much to say here.

A couple of clarifications about the above before we continue. The input “port”, if you would like to call that way, of a stream is called a sink, whereas the output “port” is called a “stream”(read that again if you don’t want to get confused later on).

Also, what is that UiEvent class? Well, as I have said, events can be of different types. We generally define each type of event as subclasses of a common abstract class.

abstract class UiEvent{}class GetNewDataEvent extends UiEvent{}class ReloadDataEvent extends UiEvent{}class ResetDataEvent extends UiEvent{}

Although the same could be done by using enums:

enum UiEvent{
GetNewData,
ReloadData,
ResetData
}

So the event stream would handle all events extending the UiEvent class(or is a member of the enum). You could define multiple BLoCs this way, each with their own streams, handling a specific type of event, if you wish.

You can notice the use of getters for the ease of use in our UI code.

Pretty self-explanatory, right?

Okay, how about looking at the UI side?

So as you can see, implementing BLoC pattern in Flutter is easier than you think.

But implementing the above approach does require some hoops to jump through:

  • We have to create and dispose of the StreamControllers along with the instantiated objects in the UI code manually, which let’s be honest, no one wants to do.
  • Passing the BLoC object through constructors to widgets deep inside the widget tree does not scale well.

How do we solve that?

Introducing: flutter_bloc

This package solves the above two problems this way:

  • By abstracting away the boilerplate.
  • By using the power of Provider, another well-known alternative for state management(although it is not necessarily made to be one).

Let’s look at the same code when implemented using flutter_bloc:

Three observations here:

  • Don’t get startled by the use of async* and yield in mapEventToState. As far as this article is concerned, it just denotes that the state objects are generated in the state stream to be passed to the UI.
  • mapEventToState returns the state stream itself.
  • If you are wondering where do we dispose off the stream we created, don’t worry. You won’t find any. The package takes care of that by itself!

Using this package, our class came down from 29 lines to 16 lines, which isn’t saying much, but considering we are not required to manage streams manually, that’s something.

One more thing to say, the state objects that are getting passed to the UI are distinct every time so that it enforces the immutability of states, which is nice to have (I wouldn’t elaborate why this is desirable here, maybe in a future article?) 😉.

The second solution becomes apparent when we switch to the UI side:

If you have used Provider before, the above syntax will be very familiar to you. In case you have not, let’s just say it helps to find the MyBloc object from anywhere in the widget tree below it much easier.

And, if you are thinking of using RxDart to expand upon the functionalities, you can do that too. Flutter_bloc has transform() which gets executed before any event goes to mapEventToState(), where you can implement all the RxDart goodness.

And there you have it. Flutter BLoC, from the perspective of a beginner.

If you liked this article, do clap to show your appreciation! You can even follow me on Twitter, or have a look at my Github page.

--

--

Anurag Roy
Anurag Roy

Written by Anurag Roy

I work with Flutter, occasionally with unix or frontend web.

No responses yet