Is it similar to Redux?
The BLoC architecture has been quite around for some time in the Flutter community, and we can safely say that the community loves it. How the pattern allows us to isolate our logic and make it more testable is just by doing some changes in our app is just pretty awesome stuff.
In this tutorial, we will learn the BLoC concept and its overall flow by creating a simple app, and by the end of this tutorial, we should already be able to write apps that implement BLoC architecture.
The BLoC version we’re going to use in this tutorial is 8.0.1 ( the latest version at the time of writing )
There are three core components of the BLoC architecture:
States are every condition our page or app might be. For example, if we fetch API from a page, then our page will at least have three states:
LoadingStateIn this state, we might want to show a loader on our page so the user understands that we’re trying to fetch some data.
ErrorFetchDataStateIn this state, we might want to show an error text, a retry button, or show an alert informing the user something is going wrong with our network call.
SuccessFetchDataStateIn this state, we can display a list of cards or data, based on the data we get from the response.
Events are actions that UI sends in order to mutate our page state ( sounds familiar, redux developers? )
BloC acts as action handlers and state mutators, we can do our business logic here as well, for example fetching APIs, storing shared preferences, etc. Some people call them reducers.
Judging from its concept theories, BLoC is pretty much the same as redux or the composable architecture, so if you happen to understand one of them, you have pretty much understood BLoC.
Navigate to the code editor of your choice, and create a new Flutter project with any name you may think of. Then, after Flutter finishes creating the boilerplate for us, add
flutter_bloc dependency in the
pubspec.yaml like such.
Replace everything on the
main.dart with this code
In the code above, we create a home screen widget with a button in the center of the page. Pretty straightforward.
What we are going to do next is we are going to add several functionalities to our app, our final app could display a list of food cards or an error message depending on the response we get from our API.
For the sake of simplicity, we are not going to hit any real endpoints, we’re going to just random the result every time the user pressed the button.
Create a new file named
food.dartthis file will contain the model for our food card.
Create another new file named
food_generator.dart, this file will provide us with all of the dummy data displayed on the app. Here’s the code:
Next, let’s proceed to our first BLoC-related code, here we are going to create the classes for our state and events.
Create a new file named
home_state.dart and add the following code inside the file:
From the requirements, our app can only do one thing at the moment, which is trying to fetch data when the button is pressed. From there, we can imagine that our page should at least have three states:
This is the state when we’re waiting for the API response, we might want to show some kind of loader indicator or shimmer to let the user know that it’s still processing.
This is when we receive an error from the API, and we might want to show an alert or display some error widgets.
This is when we successfully receive our food data from the API, we might want to show a list of food cards here.
Besides the three states mentioned above, we also have to create another
HomeInitialState. This is the first state of the page since we did not want to show the loader when a user views the page. More on this below.
Next, we should define the actions that the page can send, you can imagine actions are anything that triggers state changes. Create a new file named
home_event.dartand add this code below.
Since our home page can only do one thing at the moment, fetch data, we have to create this fetch data event as one of our
HomeEvents. Remember sending events is how our widget tells the BLoC that they want to do something.
So, for example, if you want to hit trackers when we view the page, you can add another event here.
Finally, we have to create a BloC class, this class acts as an action handler that does all the business logic and gives the result back to our page through its state construction. Create a new file called
home_bloc.dart and add the following code:
HomeBloc extends the base Bloc class which expects two parameters: our event and state classes. This is to tell the Bloc that we are dealing with events and states of type HomeEvent and HomeState respectively, which answers the question of why we create them as abstract classes.
Inside the constructor, we should provide the class with our initial state via its super constructor. Since we are not doing anything when viewing the page for the first time, we can just make another empty state, in this case, the
But if, for example, you want to directly show shimmer when user opens the page, you might consider setting
HomeLoadingState as its initial state in the constructor.
Inside the super constructor closure, we have to list down all the events that might be sent to our
HomeBloc, and since we only have one action, we only put one. Or if you prefer a more friendly explanation, line 6 can be understood as follows:
When the HomeBloc receives a
FetchDataEventit will try to handle the event by executing the handler given to it, in this case, the
Now, what do we do inside the
Remember that we want to fetch data and return the response to our home page. In this case, I will just delay the function execution for two seconds to simulate network calls and generate a random boolean to determine whether we got a list of food or an error.
And while processing all those logic, we want to show a loader on our page, so the algorithm will be like this:
- change state to
- delay the function execution for two seconds to simulate network call
- random a boolean, if it is true, we change state to
HomeSuccessFetchDataStatebringing a list of dummy food, otherwise, we change the state to
Add some changes to the
Congratulations! We have just finished our BLoC code.
One final thing to do is modify our widget (home screen) so that it can listen to the state mutations happening inside the BLoC. With this done, we can change our UI accordingly to our state (eg, showing some try again button when an error happened, etc.).
Fortunately, the BLoC library has us covered. All we have to do is wrap our widget in a
BlocConsumer widget and we’re ready to go!
main.dart and change the code to the following:
BlocConsumer requires us to define two properties, namely listener and builder.
The builder block of code will be triggered every time the state changes. Therefore, we have to list all the possible states, which is why we can see a lot of
if state is ; it is because we have to specifically handle each one of the states.
The listener block will also be executed when the state changes. The difference between listener and builder is that the builder expects us to return a widget. In other words,
what kind of UI you want to show to the user when the state is X, Y, or Z . On the other hand, the listener does not require us to return a widget. This is the place where we do side effects, such as showing alerts, snack bars, hit trackers, etc.
Add the widgets on each of the states inside the builder, so that our HomeScreen becomes like the following:
HomeLoadingStateI just returned a
CircularActivityIndicator as the loader for the
HomeSuccessFetchDataStateI return a
ListView so the user can see all of our foods, and we return an error text with a retry button on the
Next is, we have to wrap our
HomeScreen inside a
BlocProvider in the
runApp function. This should be done to provide the
HomeScreen and all of its children access to the bloc. Otherwise, we will get an error stating the BLoC is not found.
After that, we have to also add the send event capability on our home widget, in order to do so. We have to grab the
HomeBloc inside the HomeScreen widget, and because we have already provided them via the
BlocProvider we can read them from the context.
HomeBloc property and an
initState function in the HomeScreen widget like this:
And finally, add the event sending code inside every
main.dart will look like this:
Try to run the app, and voila!, our app should be finished with the requirements fulfilled!
BLoC is pretty much the same as redux or the composable architecture, all of them have one instance that acts as an action handler where we do all of the business logic there. And the UI sends state-mutating actions and listens to state changes to perform necessary updates.
You can find the finished code in my repository here:
You can learn more about BLoC at their official documentation here: