Sitemap
Better Programming

Advice for programmers.

A Comprehensive Guide to Flutter’s BLoC Architecture

7 min readJun 9, 2022

--

Photo by James Wainscoat on Unsplash

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 )

Some Theories Regarding Bloc

There are three core components of the BLoC architecture:

  1. States
    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:
  • LoadingState
    In this state, we might want to show a loader on our page so the user understands that we're trying to fetch some data.
  • ErrorFetchDataState
    In 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.
  • SuccessFetchDataState
    In this state, we can display a list of cards or data, based on the data we get from the response.
  1. Events
    Events are actions that UI sends in order to mutate our page state ( sounds familiar, redux developers? )
  2. BloC
    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.

Let’s Code

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 centre 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.dart, this 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:

  1. HomeLoadingState
    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.
  2. HomeErrorFetchDataState
    This is when we receive an error from the API, and we might want to show an alert or display some error widgets.
  3. HomeSuccessFetchDataState
    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:

home_bloc.dart

Our 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 HomeInitialState.

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 FetchDataEvent, it will try to handle the event by executing the handler given to it, in this case, the _onFetchDataEvent function.

Now, what do we do inside the _onFetchDataEvent?

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:

  1. change state to HomeLoadingState
  2. delay the function execution for two seconds to simulate network call
  3. random a boolean, if it is true, we change state to HomeSuccessFetchDataState, bringing a list of dummy food, otherwise, we change the state to HomeErrorFetchDataState.

Add some changes to the _onFetchDataEvent function:

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 (e.g., 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!

Navigate to 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:

For HomeLoadingState, I just returned a CircularActivityIndicator as the loader for the HomeSuccessFetchDataState, I return a ListView so the user can see all of our foods, and we return an error text with a retry button on the ErrorState.

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.

Add a HomeBloc property and an initState function in the HomeScreen widget like this:

And finally, add the event sending code inside every onPressed function:

Our final main.dart will look like this:

Try to run the app, and voila!, our app should be finished with the requirements fulfilled!

Conclusion

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:

Happy coding!

--

--

Arya Surya
Arya Surya

Written by Arya Surya

Mostly writes about frontend, including but not limited to iOS and web development. Go follow @agustinustheoo for other stuff

Responses (1)