Since BLoC (Business Logic Component) pattern was presented in Google I/O 2018, many articles were written about this topic, like this one, and this bloc library that is heavily influenced by Redux reducers, and many more.
After watching the Google I/O video again, my understanding of BLoC is that it’s a disciplined way to access and update data among widgets in the tree, like a global variable.
Obviously, there is more than one way to implement BLoC. After thinking a bit more, I found that we didn’t have to use the ReactiveX/rxDart or Streams. They come with their own jargons and building blocks by themselves, which leads to a certain learning curve.
If you are curious, ChangeNotifier is part of the Flutter foundation library, so we won’t have to depend on third-party libraries like rxdart.
The app we will build is a simple Gift Cards Shopping Cart app.
The requirements are straightforward:
- There are six gift card items on the main page. Whenever a user clicks on an item, the app adds it to the shopping cart.
- The top right shopping cart icon comes with a badge that shows the current total count of items in the cart.
- Clicking on the shopping cart icon will navigate the app to the shopping cart details page. It has a breakdown of how many cards are added.
- Clicking on the Clear button on the details page clear the item count.
To build this app, one tricky thing is that the main and the shopping cart page both can read and change the shopping cart object. Users expect that, if one page changes the cart, the other page would see the updated cart right away.
This type of cross-page state sharing is commonly seen in many other types of apps. BLoC pattern is designed to solve it. We can think of a BLoC as a global variable that gets created at the root widget, passed down to the children in the widget tree, and is accessible to all its children.
Now let’s move on to talk about the code.
First, let’s see how BloC is used in the main page. The code has the following structure:
If we focus on the page logic that renders the UI, we can see that the page:
- reads the total count from a bloc object, provided by Provider
- renders the count and images in the page
There are two user interactions here:
- When a user clicks on the image, it triggers
bloc.addToCartfunction to update the cart.
- If a user clicks the shopping cart icon in the AppBar, the app simply navigates to the shopping cart page. No state is explicitly passed in the
The shopping cart page has the similar structure:
Similarly, the cart page reads the count from the bloc, and calls the
bloc.clear() function if a user clicks on the clear button.
Finally, the bloc implementation:
What CartBloc does here is simple: It has an internal cart object to keep track of the current cart. We can see
cart as a global variable that is shared among the widgets / pages.
The final step to hook up the BLoC, so that it can be accessed by all pages:
UPDATE: As pointed out Josephe in the comment, calling
build()function will lead to rebuilding the page whenever the provided object’s state changes. In this toy example, the page isn’t that complicated, so it can afford to rebuild many times. For complex pages where rebuilding is expensive, we can consider to use
Consumer / Selectorto selectively rebuild parts of the page. It is documented in the Provider readme.
Thank you for your read!
All source code is available in Github.
Questions? Feel free to drop me a note.