It’s almost inevitable to come across the concept of or at least the term “BLoC” when you learn about Flutter and its recommended architecture approaches. However, depending on their personal previous experience with Dart, Flutter, Streams and state management, it might be hard for beginners to wrap their head around it. During this article I want to clear things up.
What does BLoC mean?
Generally speaking, BLoC (Business Logic Component) is a design pattern enabling developers to efficiently and conveniently manage state across their apps without a tight coupling between the presentation (view) and the logic. It also aims for reusability of the same logic across multiple widgets. It was first mentioned by Google at the Google I/O in 2018 and has since then become the de facto standard for many developers when it comes to state management solutions in Flutter app development.
Why do we need an app architecture?
The above explanation might sound reasonable but also quite complicated for a problem that can be solved in totally simple ways, right?
Yes and no. Yes for tiny apps and private projects. No for apps that need to be scalable, readable and maintainable and is worked on by more than one person. At the beginning you can save quite some time if you go for a very simple approach without too many thoughts about the architecture. However, you will reach a point where the whole code becomes a mess in terms of testability, maintainability and probably also performance. At this point, a clean architecture will have paid off.
The problem is: once you start a project, you never know, how big it’s going to become eventually. In my opinion, it’s a good idea to go for the safe approach and cleanly design the architecture right from the beginning as it can save a lot of trouble later on.
When a project has reached the point where it has become hardly maintainable, it is a huge load of work to transform it into a well structured project. Even worse: I have often made the experience that companies decided to rewrite everything because the effort of a huge refactoring would even exceed the effort of a complete rewrite.
What problems does it solve?
Given the assumption that an architectural pattern like BLoC improves the long-term maintainability of software projects, what are the concrete problems it solves?
State management
When mobile app projects gain a certain size, there is an increasingly high probability that screens (in Flutter: widgets) share data across the whole app. Like a shopping cart with its items or the status of being logged in. If this kind of app state is changes very close to the root of the widget tree and needs to be passed down at least with a depth of 3, it becomes very cumbersome to always pass this information down in the constructor and along with that a callback function that is being called when the nested widget registers a user interaction which in response is expected to change that state.
Separation / reusability
Another issue being raised when developing a software without a structuring pattern is the separation of view and logic. There are well-known examples of architectural patterns that enforce this separation like MVC, MMVM. You might ask yourself: why do I need that separation? Isn’t it fine to put the logic directly into the widget? In the end, that’s where it’s needed!
The problem with that is: as long as you only have one screen with one or a couple of widgets, this might be fine. But as soon as your application grows, this becomes a massive obstacle. Example: let’s say you have implemented a screen that has a header, a list of elements and an input field at the bottom. Then you realize that you need another screen that is similar (the look is quite the same) to the one you already have, but not equal (the behavior is different). How do you re-use the list of elements and the input field if you only want the visuals to be the same but not the behavior? There is a tight coupling between the behavior and the visuals which make it impossible for you to reuse it.
Testing
Another issue that comes up: if you hard-wire the logic to the view, you are unable to write white box tests such as unit tests for the logic. Example: there is a button that executes a request to an external API on press. If an error occurs, a SnackBar should display: “Oh snap, there was an error!”. Being unable to mock the class that executes the request, you are incapable of testing the scenario because you can’t setup a test case where it responds with an error. Even if you inject this class into the widget, you can only make assumptions about the widget tree inside a widget test (expect that the SnackBar
is shown), but you can’t directly test if the logic properly handles the error.
Using BLoC enables you to write unit tests for the BLoC classes in which you make assumptions about the emitted streams given some events. For widgets that depend on certain BLoC classes, you can write widget tests in which you mock the BLoC class (making it return a certain state) and make assumptions about the widget tree given that particular state. This way you know that a) the inner logic of the BLoC class is working and b) the widget reacts properly on the state of the BLoC. Last but not least, you’re capable of writing integration test in which you can also mock the BLoCs, preventing them from making actual HTTP calls or accessing any kind of data layer that is not available during testing
In summary, a clean separation leads to views acting like: “I don’t know about behavior and I don’t care. I just present what I get” and BLoCs acting like: “I don’t know about presentation and I don’t care, I just receive orders and respond with a state according to my logic”.
How does it work?
The idea behind BLoC (which is the name of the pattern and the name of the component within the pattern) is that a widget always knows the state of the BLoC it currently relies on (might also be multiple BLoCs). When the widget wants the state to change (e. g. in response to a user interaction), it tells the BLoC that something has happened (“login was triggered”). The BLoC reacts according to its defined logic and emits n states asynchronously (“loading”, “login successful”). Every time a state is emitted from the BLoC, the parts of the widget which are dependent on that particular BLoC state, update accordingly. This is a repeating circle.
As a consequence, the view layer (consisting of widgets) does never directly communicate with the data layer (consisting of API calls or database queries). Instead, it delegates this responsibility to the BLoC layer which acts as a middle man between the two.
Technically speaking, BLoC builds upon streams. A stream is basically a metaphor for asynchronously flowing data from an issuer. This flow of data can be listened to. The issuer decides when to emit a new event which notifies all listeners. Although technically not correct, it might be helpful to imagine a stream like a sequence of Futures.
Difference between the pattern and the package
Although the BLoC pattern is one of the officially recommended state management approaches, there is no direct native support from Flutter. Instead, you either implement it yourself or go for a package that has already implemented all the boiler plate code for you. This is where the bloc package comes into play. It provides all core classes that are necessary to use the pattern. I would absolutely recommend to use it instead of implementing your own solution.
Don’t we already have proper solutions for that?
If you have followed the official basic flutter tutorials, you probably also stumbled across this one. It suggests to use StatefulWidgets
and setState()
to manage the state. Also, when you create a new project, it’s pre-coded with an app that counts the number of taps on a floating action button. It also uses StatefulWidgets
and setState()
. For more information about the difference between StatelessWidgets
and StatefulWidgets
, check this out.
If there is a built-in method for that, then why bother learning something new? And when it comes to passing data (state) down the widget tree, why don’t we just use InheritedWidget, which is also a built-in class?
The main reason why this is not a good idea, is that it’s not meant to hold a global state. Its purpose is rather to manage a state with very limited influence like an active state of a checkbox. That’s because using a StatefulWidget
, you are incapable of separating the state from the presentation layer as the state is tightly coupled to the widget it’s bound to.
You can use this approach for limited state (or ephemeral state, as the official docs name it) inside a widget, but even this has the downside of it not being testable. If you have a very simple state variable being only read and written to in one or two places with no complex logic involved, you can think about using it. It can also be the way to go when you have just started learning Flutter and just want to grasp the basics. Otherwise there’s no situation, in which I would recommend it.
When to use BLoC?
You should consider the usage of the BLoC pattern:
- When you’re about to create a
StatefulWidget
or writesetState()
- When your widget needs to communicate with the data layer (e. g. a repository, a service, an API call)
- When the logic of a widget is managed by the widget itself and you have the feeling that it has become too complex
- When you want the logic to be completely independent of the widget
- When you want to automatically test your widget’s logic
- When you start a project and you know it’s going to have more than a couple of screens and need it to be scalable and maintainable
Examples
This bloc has some tutorials that utilize the BLoC pattern (and the BLoC package) and show how to implement it in the context of an actual example (calculator app):
- A general tutorial about bloc pattern in practice
- A tutorial that builds upon the former and adds service communication
Are there alternatives?
As you might have guessed, the BLoC architecture is not the only option you have when you want to have state management. An officially recommended state management system is Provider by Remi Rousselet. Compared to BLoC, it has a reduced amount of boiler plate code. However, it’s not a complete alternative to BLoC. The BLoC package for example even uses the provider package (when injecting BLoCs into widgets, you use a BlocProvider). Provider is in a way just a more convenient version of InheritedWidget which aims at passing data down the widget tree without directly passing it down from child to child.
If the BLoC pattern describes an architecture that solves the problem of interaction between views and data layer, then Provider is a tool to simplify the injection of the classes (such as BLoC classes) into the widgets where it’s needed.
Provider is much easier to grasp for most beginners as it’s not so hard to understand that you have a model that can be accessed from a widget and its changed can be listened to. For smaller projects and a not so complex architecture, this might be a suitable choice.
Coming from the web scope, you might want to have a look into the Redux adaption for Flutter.
Conclusion
State management is a rather confusing topic. That’s because the answer to the question: “How do I perform adequate state management in Flutter?” is probably “it depends”.
It depends on previous experience, size and complexity of the project, personal preference, willingness to learn and several other factors. What I can say though: when implementing the BLoC architecture by using the bloc package, the worst thing that can happen is that you write lots of boiler plate code. Although, since version 6 of the package, it has been reduced (see cubits). If you choose something too simple like setState()
(which I wouldn’t recommend in any case), then the worst thing that can happen is that you need to rewrite the whole code.
In the end, I think there will always be different solutions which is a good thing because there will always be different preferences and opinions.
Godwin
Marc
In reply to Godwin's comment
Ajlan