Cubit 101: What is It, How to Use it and More
There is much we could say regarding Cubit, but in this post, we’ll focus on what Cubit is, its differences with Bloc (Business Logic Component), why it can be extremely useful, how we can use them and how they look when applied in a real-world scenario. The CreateThrive mobile team and I started using it and we love it!
What is Cubit?
Cubit is a minimal version or a subset of the BLoC design pattern that simplifies the way we manage the state of an application. To do so, it substitutes the use of events (used in Bloc) with functions that rebuild the UI by emitting different states on a stream. Felix Angelov proposed merging Cubit into Bloc:
“It was brought to my attention several weeks ago that bloc was being used in a different way by various members of the community. Rather than adding events to the bloc, many developers found it simpler to just invoke methods on the bloc in which new states could be added.”
At a more technical level, a Cubit is a class that contains an observable state. This observable works based on the concept of streams but in a super “developer-friendly” way.
Bloc’s main disadvantage is the fact that it’s event-driven and not all states benefit from that. Most of the time, this path introduces unwanted, extra complexity and code. Cubit, on the other hand, is function-driven, avoiding all the complexity of handling events introduced by Bloc.
Still, this doesn’t mean Cubit will overtake the use of traditional BLoC with events. Both can be used in different cases, but that is a blog for another day. In short, Cubit is a simpler state management solution.
You can get and use flutter_bloc in your Flutter project right away.
Why Cubit
As developers, we are always looking for the best ways to write our code. More specifically, we want to:
- know what the state of our application is at all times, anywhere.
- easily test your app.
- make decisions based on every user interaction.
- reuse our code within or outside our code.
Every single one of these points is covered by Bloc and using it indirectly allows us to separate the presentation from the logic, offering several different advantages.
Cubit, as a state manager, helps (you guessed it) to manage the state, which is critical when trying to build high-quality applications. This is essential for solving common problems, such as sending information between different widgets.
When and how to use Cubit
Context: We have a small widget tree where A is the parent. We want to send part of the data in A to the children. In order to do that, we can pass that information to the constructor of each widget.
Problems:
- To get that data to Widget D, we need to add the data we want to send down to every constructor of every parent of D and when more children that need that data are added to the tree, those widgets and their parents would need to receive that data in the constructor as well. However, we are passing data to widgets that don’t actually need it (Grey nodes).
- Widget C is dependent on values that are altered whenever something happens in Widget B.
Solution: Bloc pattern! More specifically, Cubit. So, how does Cubit solve these problems?
Bloc uses the package provider for dependency injection. If we want to use a Cubit, we first need to inject it into our tree. In order to do so, we are going to use the BlocProvider Widget.
BlocProvider
This is a Flutter Widget that creates and provides a Bloc to all its children (also known as a dependency injection widget). This way, a single instance of a Bloc can be provided to multiple widgets within the sub-tree. In short, the entire subtree will benefit from a single instance of a Bloc injected into it and the entire subtree will be dependent on the Bloc we are providing.
However, keep in mind that widgets placed above the widget where the BlocProvider is added will not be able to access the Cubit.
First, we need to create a Cubit.
As you can see, we have a CounterCubit that extends Cubit<int>. Note that, in this Cubit, we can only manage an ‘<int>’ as the state. If we want to manage a more complex structure, we could create another class called CounterState and replace the ‘<int>’ with ‘<CounterState>’ and use the state of the Cubit as such.
In this Cubit, we only have the constructor and two methods. Each method calls an emit function with a new state value (in this case ‘<int>’), which updates the state to the provided one (for example state +1). If the state emitted is equal to the current state, the emit function does nothing.
Remember that in order to use the Cubit, we need to provide it first. The BlocProvider widget can be created in the following way:
Once we provide it, every widget and sub-widget inside CounterView() will have the Cubit available to use.
Once that is out of the way, we have 3 ways to consume the cubit.
- BlocBuilder
This is a widget that, as the name suggests, rebuilds the UI based on changes made to the Bloc/Cubit state. Rebuilding the UI may take a lot of time to compute, so we strongly recommend wrapping ONLY the widget we want to rebuild, avoiding unnecessary re-renders.
Syntax-wise, BlocBuilder requires a Bloc or Cubit and a builder function. The builder function should be a pure function. This means that everything inside the builder has to depend on the context, the state and nothing else.
So, whenever the state of that Cubit changes, the build function is called and everything inside the BlocBuilder will be rebuilt. We can also add another optional function called ‘buildWhen’, that limits the builder function to execute only once a certain condition is met.
- BlocListener
This Flutter widget also listens to any state change. It is similar in some ways to the BlocBuilder but it does have some differences. For starters, the BlocListener is only called once per state, not withstanding the initial state. This widget also has an optional ‘listenWhen’ function to tell the BlocListener exactly when to call the listener function.
- BlocConsumer
This widget combines both the BlocBuilder and the BlocListener. So, if we have a class that has a BlocBuilder widget and a BlocListener widget, we can instead use BlocConsumer.
We just need to copy the content of the listener function (from BlocListener) and paste it into the listener function of the BlocConsumer, as well as copy the content of the builder function (from BlocBuilder) and paste it into the builder function of the BlocConsumer. And voilà! We have a more legible code.
- BlocSelector
This widget is very similar to the BlocBuilder cubit with a small difference. It receives an extra state (SelectedState) and you need to return (from the current state) the selectedState you want to listen to. This avoids unnecessary builds.
Example
Conclusion
If we are trying to build a high-quality app, it is essential that we plan how to manage the state of the application, and there is no better way of doing that than through Cubit.
It is an extremely simple yet powerful solution that makes state management a walk in the park. It also provides all the necessary tools to react to those state changes anywhere in the application, making it extremely dynamic and easy to code.
There are no reasons to not use Cubit. It is a hassle-free, time-saving solution. You might be also interested in: How Flutter changed Development for the Better.
References
https://www.udemy.com/course/bloc-from-zero-to-hero/learn/lecture/25183780#overview
https://medium.com/flutterdevs/cubit-state-management-flutter-d372ec0044f7
https://pub.dev/packages/flutter_bloc
https://github.com/felangel/cubit/issues/69