Bloc is a well-known and established library when it comes to state management in Flutter. It promotes good practices such as immutability and it has one of the best ecosystems of supporting packages and documentation built around it. In spite of all these benefits, using the Bloc package is painful at times and the cause is none other than boilerplate.
The version 6.0.0 and upwards of the Bloc package comes to make this library palatable to the masses! It gives you the ability to use a lighter version of Bloc called Cubit, and removes a bunch of boilerplate.
Let's build an app
This tutorial is suited both for beginners who'd like to learn Cubit/Bloc from scratch and also for developers who are already experienced with Bloc. If you'd just like to see a migration guide from the previous version, there's no better place to look than the official Bloc website.
It's best to learn from real examples and that's why we're going to build a simple weather forecast app at first using Cubit and then with Bloc (going between the two is very simple). Grab the starter project from below which contains all the code like UI and model classes not related to state management.
The app displays a randomly generated temperature in the given city which allows us to demonstrate asynchronous fetching of data. We'll also show a loading indicator while awaiting a Future
and an error snackbar whenever an exception is thrown while fetching the forecast.
Let's now take a brief look at the already implemented classes. This app is centered around a model class called Weather
.
weather.dart
class Weather {
final String cityName;
final double temperatureCelsius;
Weather({
@required this.cityName,
@required this.temperatureCelsius,
});
// == and hashCode overrides...
}
This weather will contain a randomly generated temperature gotten from a FakeWeatherRepository
. There's also a chance that a NetworkException
will be thrown instead.
weather_repository.dart
abstract class WeatherRepository {
/// Throws [NetworkException].
Future<Weather> fetchWeather(String cityName);
}
class FakeWeatherRepository implements WeatherRepository {
@override
Future<Weather> fetchWeather(String cityName) {
// Simulate network delay
return Future.delayed(
Duration(seconds: 1),
() {
final random = Random();
// Simulate some network exception
if (random.nextBool()) {
throw NetworkException();
}
// Return "fetched" weather
return Weather(
cityName: cityName,
// Temperature between 20 and 35.99
temperatureCelsius: 20 + random.nextInt(15) + random.nextDouble(),
);
},
);
}
}
class NetworkException implements Exception {}
Quick introduction to Cubit
Although there is a bigillion different ways to manage state in Flutter, in essence, it all boils down to having mutable or immutable state. We probably all get started mutating individual fields inside a State
object of the StatefulWidget
or inside a ChangeNotifier
. Methods that mutate state are inside the same object as the state itself.
mutable_example.dart
class MyChangeNotifier extends ChangeNotifier {
int field1;
String field2;
void changeState() {
field2 = 'New value';
notifyListeners();
}
}
An immutable equivalent of this simple ChangeNotifier
built with Cubit
would look like the following.
immutable_cubit_example.dart
class MyState {
final int field1;
final String field2;
MyState(this.field1, this.field2);
// == and hashCode overrides...
}
class MyCubit extends Cubit<MyState> {
MyCubit() : super(MyState(0, 'Initial value'));
void changeState() {
emit(MyState(0, 'New value'));
}
}
Instead of mutating individual fields, we emit
whole new MyState
objects. Also, the state is in a class separate from the one responsible for changing the state.
Stream
under the hood to deliver newly emitted states to the UI. This is more of an implementation detail because unless you need to do something advanced, you're not going to use the low-level Stream
interface all that much.Cubit vs Bloc
If you've worked with Bloc before, you can see that something is missing - events. They're replaced by regular methods. Instead of adding an event to the Bloc called GetWeather
, you're going to call a method on the Cubit called getWeather()
. Thus, the code will become shorter, cleaner and you'll have to maintain fewer classes.
If you are totally new to the Bloc package though, don't worry, as we're going to cover the "old-school" Bloc later on in this tutorial.
Building the app with Cubit
The first step is to add dependencies to pubspec.yaml. Both Cubit and Bloc are interoperable, in fact, the Bloc
class extends Cubit
. This means we need to import only the bloc and flutter_bloc libraries and we're going to get Cubit bundled for free.
pubspec.yaml
dependencies:
flutter:
sdk: flutter
bloc: ^6.0.1
flutter_bloc: ^6.0.1
Next, we need to create the files that'll hold our WeatherCubit
and also WeatherState
classes. It's possible to create all these files and boilerplate manually but there are also handy extensions for VS Code and IntelliJ/Android Studio. We'll use VS Code in this tutorial.
After you've installed the extension, right click on the lib folder and select Cubit: New Cubit. Give it a name "weather". You should now see the following in the explorer:
WeatherState
A rule of thumb is to always build out the state class(es) first. They're the reason why you want to create a Cubit in the first place, right? Without a proper representation of the state of the Cubit, you can't possibly write logic that will emit new states. So, what kind of WeatherState
do we want to have?
Well, we're going to be asynchronously loading a single resource - the Weather
model. In such occasions, it's best to represent the state as multiple subclasses of the WeatherState
abstract class. If we take a look at the weather_state.dart file generated by the extension, we can see that one such subclass has already been created for us:
weather_state.dart
part of 'weather_cubit.dart';
abstract class WeatherState {
const WeatherState();
}
class WeatherInitial extends WeatherState {
const WeatherInitial();
}
This WeatherInitial
state will indicate that no action has yet been taken by the user and that we should display an initial UI. So, what other state subclasses should there be for asynchronously loading the weather? Well, we need to be able to show a progress indicator while we're awaiting the data and then handle success or a possible error.
weather_state.dart
class WeatherLoading extends WeatherState {
const WeatherLoading();
}
class WeatherLoaded extends WeatherState {
final Weather weather;
const WeatherLoaded(this.weather);
@override
bool operator ==(Object o) {
if (identical(this, o)) return true;
return o is WeatherLoaded && o.weather == weather;
}
@override
int get hashCode => weather.hashCode;
}
class WeatherError extends WeatherState {
final String message;
const WeatherError(this.message);
@override
bool operator ==(Object o) {
if (identical(this, o)) return true;
return o is WeatherError && o.message == message;
}
@override
int get hashCode => message.hashCode;
}
The code above may seem long but that's only because of overriding referential equality with value equality. Since this is a tutorial about the Bloc package, I don't want to use additional packages. However, in real projects I always use freezed unions which makes the code much shorter and safer.
WeatherCubit
Having finished our work on the state, let's implement the WeatherCubit
that will perform logic such as getting the weather from the WeatherRepository
and emit states. Some code has already been generated by the extension:
weather_cubit.dart
part 'weather_state.dart';
class WeatherCubit extends Cubit<WeatherState> {
WeatherCubit() : super(WeatherInitial());
}
Passing WeatherInitial
to the super constructor makes it, surprisingly, to be an initial state. This is what the UI will operate with until the user searches for a city.
We now want to add a WeatherRepository
dependency to this class and also implement a getWeather
method.
weather_cubit.dart
part 'weather_state.dart';
class WeatherCubit extends Cubit<WeatherState> {
final WeatherRepository _weatherRepository;
WeatherCubit(this._weatherRepository) : super(WeatherInitial());
Future<void> getWeather(String cityName) async {
try {
emit(WeatherLoading());
final weather = await _weatherRepository.fetchWeather(cityName);
emit(WeatherLoaded(weather));
} on NetworkException {
emit(WeatherError("Couldn't fetch weather. Is the device online?"));
}
}
}
The above code is quite self-explanatory. We use Cubit's emit
method to, well, emit new states.
User interface
We first need to provide the WeatherCubit
down the widget tree. In the starter project, we'll do so by wrapping the WeatherSearchPage
with a BlocProvider
.
main.dart
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: BlocProvider(
create: (context) => WeatherCubit(FakeWeatherRepository()),
child: WeatherSearchPage(),
),
);
}
}
BlocProvider
for a Cubit
? Yes! All of the widgets from the flutter_bloc package have the name "Bloc" inside them and since Bloc
extends Cubit
, they work with both.As you could have seen in the video at the beginning of this article, the app will either show only a city search bar for initial and error states, a progress indicator for the loading state, and lastly, the temperature and city name for the loaded state. Additionally, we want to show a SnackBar
if an error occurs.
All of the widgets are already prepared in the starter project. We just need to connect them to the states emitted by the WeatherCubit
using a BlocBuilder
. Check the type of the incoming state and return widgets accordingly.
weather_search_page.dart
class WeatherSearchPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Weather Search"),
),
body: Container(
padding: EdgeInsets.symmetric(vertical: 16),
alignment: Alignment.center,
child: BlocBuilder<WeatherCubit, WeatherState>(
builder: (context, state) {
if (state is WeatherInitial) {
return buildInitialInput();
} else if (state is WeatherLoading) {
return buildLoading();
} else if (state is WeatherLoaded) {
return buildColumnWithData(state.weather);
} else {
// (state is WeatherError)
return buildInitialInput();
}
},
),
),
);
}
// more code here...
}
How about showing a SnackBar
when WeatherError
is emitted or doing any other sideeffect though? We certainly can't show do during a widget build or we're going to get the well-known setState() or markNeedsBuild() called during build error message and a red screen of death. Even if this weren't the case, a widget can be rebuilt multiple times which could possibly cause multiple SnackBar
s to be shown even when no new error states have been emitted.
To solve this issue, we can use a BlocListener
. However, because we're already using a BlocBuilder
and we don't want to unnecessarily introduce yet another widget and nesting that goes with it, we can switch to using a BlocConsumer
! It's builder and listener combined.
weather_search_page.dart
BlocConsumer<WeatherCubit, WeatherState>(
listener: (context, state) {
if (state is WeatherError) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
),
);
}
},
builder: (context, state) {
// Previously written code here...
},
),
Don't you feel like we're missing something? Of course! We haven't yet called the getWeather
method. Let's do so from the CityInputField
widget all the way down in the weather_search_page.dart file.
weather_search_page.dart
class CityInputField extends StatelessWidget {
// Code here...
void submitCityName(BuildContext context, String cityName) {
final weatherCubit = context.bloc<WeatherCubit>();
weatherCubit.getWeather(cityName);
}
}
We're using the new fancy bloc
extension on a BuildContext
class but you can just as well use the classic approach of calling BlocProvider.of<WeatherCubit>(context)
.
Switching to a Bloc
The app is now fully finished... with Cubit
. Without changing any of its functionality, let's now switch to Bloc
- it's going to be quick and easy and we'll be able to reuse the majority of code. In this tutorial, we're going to leave in both implementations for easy comparison.
As previously, right click on the lib folder and now select Bloc: New Bloc. Give it a name "weather". You should now see this in the explorer:
States are going to be identical with the Cubit implementation so you can copy and paste the previously written code into the new weather_state.dart file.
Next up, let's take a look at events. We already know which actions the WeatherBloc
should perform. In the case of WeatherCubit
, we had a getWeather
method. Now, we're going to have a GetWeather
event class.
weather_event.dart
part of 'weather_bloc.dart';
@immutable
abstract class WeatherEvent {}
class GetWeather extends WeatherEvent {
final String cityName;
GetWeather(this.cityName);
}
WeatherBloc
implementation yet! As I mentioned in the beginning though, events are useful if you want to track them or if you want to transform them, usually using debounceTime
operator from RxDart.Implementation of the WeatherBloc
will be very similar to the one of WeatherCubit
, however, we will need to handle a GetWeather
event inside a mapEventToState
asynchronous generator method instead of having a simple getWeather
method directly.
weather_bloc.dart
part 'weather_event.dart';
part 'weather_state.dart';
class WeatherBloc extends Bloc<WeatherEvent, WeatherState> {
final WeatherRepository _weatherRepository;
WeatherBloc(this._weatherRepository) : super(WeatherInitial());
@override
Stream<WeatherState> mapEventToState(
WeatherEvent event,
) async* {
if (event is GetWeather) {
try {
yield WeatherLoading();
final weather = await _weatherRepository.fetchWeather(event.cityName);
yield WeatherLoaded(weather);
} on NetworkException {
yield WeatherError("Couldn't fetch weather. Is the device online?");
}
}
}
}
Basically, Cubit hides its Stream
of states behind an interface where we call the emit
method. With Bloc and its events, we use the yield
keyword which is built into Dart instead.
Bloc UI
How about the UI? The changes will be minimal. We, of course, need to provide the WeatherBloc
from main.dart.
main.dart
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: BlocProvider(
create: (context) => WeatherBloc(FakeWeatherRepository()),
child: WeatherSearchPage(),
),
);
}
}
Inside the WeatherSearchPage
, delete the weather_cubit.dart import to reveal all the places that need changing. Replace all occurences of WeatherCubit
with WeatherBloc
(there are two of them) aaand... The last issue occurs. Instead of calling a method, we now have to add an event to the Bloc.
weather_search_page.dart
void submitCityName(BuildContext context, String cityName) {
final weatherBloc = context.bloc<WeatherBloc>();
weatherBloc.add(GetWeather(cityName));
}
And boom! You've just successfully implemented the same app for the second time, now using Bloc instead of Cubit. You've also learned how to pick the correct tool for the job at hand - although Cubit is simpler to use, it's not always the right choice. I'm certain that this knowledge will make state management a breeze ?
Nice and clean!???
A minor detail: you still have the ability to track changes, but there’s not an audit trail of event changes.
very nice
It is the tutorial I was waiting since the introduction of BLoC 6.0.0. It cannot be better explained. Good job!
Thank you!
Thank you, I was waiting for your cubit tutorial.
hi
when I use cubit and when use bloc en projects is my question thanks
If you want to track the states emitted (helpful in redo and undo functionality) use bloc and if you only want the lastest emitted state you can use any of them.
Hi my english is not very well but
BlocBuilder is need close ? with dispose ?
No
Why we didn’t use equatable in this tutorial as we used previously in bloc?
We are overriding equality check manually
“` @override
bool operator ==(Object o) {
if (identical(this, o)) return true;
return o is WeatherError && o.message == message;
}“`
Thats the reason why we don’t need equatable as far as I know.
Thanks for this nice tutorial.
Would you please provide a tutorial using stream subscription in the bloc version 6 as it has changed and I’m confused with that.
Matt, your tutorials are awesome! I tried implementing the BlocConsumer in my app where I need to push a new screen if the state is Loaded. Assume a scenario where I ask for registration details and if the server responded successfully I push the next screen to complete the registration. What I did was the following. What is happening is that the LoadedState portion is executed twice – once in builder and once in listener which I understand. However, before pushing the route, the buildBody appears like a flicker. Is there a cleaner way to push a route when state changes?
BlocConsumer(
builder: (context, state) {
if (state is AuthenticationEmptyState) {
return _buildBody();
} else if (state is AuthenticationLoadingState) {
return LoadingWidget();
} else if (state is AuthenticationLoadedState) {
//! This part shows like a flicker
return _buildBody();
}
},
listener: (context, state) {
if (state is AuthenticationLoadedState) {
Navigator.of(context).pushNamed(‘/enter_security_code’);
}
})
Hi Matt, thanks for the tutorial. Would you mind explaining the difference between the following 2 ways to accessing the weather cubit? I’m sure there’s a reason you did it the first way but I’m trying to get a better understanding of the difference.
final weatherCubit = context.bloc()
and
final weatherCubit = WeatherCubit()
I know it’s been 4 months, but maybe for future readers…
final weatherCubit = WeatherCubit()
This would create a new WeatherCubit(). This isn’t what we want here, because you need the events or state from the existing Bloc or Cubit that lives in the BlocConsumer.
final weatherBloc = context.bloc()
context.bloc() is now deprecated, but that was getting the existing bloc created by the BlocConsumer widget. The reason we use the context is so that we can traverse the Widget tree and find the closest defined bloc of type WeatherBloc
The new pattern uses context.read() to get the value once, or context.watch() to subscribe to changes in the value.
I replaced the line above with:
final weatherBloc = context.read()
Wonderfully clear….thank you so much Matt, for the videos and the written stuff
Thank you very much for the article. It’s really great!
I have a question. In your code for cubit you use initial state:
WeatherCubit() : super(WeatherInitial());
But what is we need to get the initial state from some API as a Future? What is the best approach?
WeatherInitial is still wrapped in a Future, if you had some default state fetched initially then that couldn’t be considered an initial state cause there is some latency involved.
Such a wonderful tutorial on BLoc and Cubit. Thank you for the video and writeup.
Thank you.
Please teach unit tests in Cubit.
first I want to thenk you for an awesome tutorial, I just want to point out that for those reading the tutorial rather than watching of the video this part is a little confusing:
“We certainly can’t show do during a widget build or we’re going to get the well-known setState() or markNeedsBuild() called during build error”
I had to watch the video to understand what you meant.
Hi! as always great tutorial! Very clear and concise. But I have some questions. 😀
1. can you use equatable for less boilerplate (hashcode and ==)?
2. which is better equatable or freezeunion?
3. What is the difference between BlocProvider and CubitProvider, BlockConsumer and CubitConsumer?
Thanks!
It took me 3 days without being able to understand the example code. But when I met this post I knew cubit and bloc. thank you for best
Are you going to show how to use Freezed with cubit?
Flutter Definition, Features, Usage, Implementation, and Forecasts check https://axisbits.com/blog/flutter-definition-features-usage-implementation-and-forecasts
Hi,
Can you please provide a follow up with how to test this app, some structure around writing tests with bloc?
Matt how do I define the bloc extension on the BuildContext?
Hi, i think that i saw you visited my web site thus i came to ?eturn the favor텶 am attempting to find things to improve my web site!I suppose its ok to use some of your ideas!!
Hi, Neat post. There is a problem along with your website in internet explorer, would test this텶E still is the market chief and a good section of other folks will pass over your magnificent writing due to this problem.
Usually I do not read article on blogs, however I would like to say that this write-up very compelled me to take a look at and do so! Your writing taste has been amazed me. Thanks, quite nice post.
My brother recommended I might like this web site. He was totally right. This post actually made my day. You cann’t imagine just how much time I had spent for this information! Thanks!
My brother suggested I might like this website. He was totally right. This post actually made my day. You cann’t imagine just how much time I had spent for this information! Thanks!
I simply could not go away your web site prior to suggesting that I really enjoyed the standard info a person supply on your guests? Is going to be back incessantly to investigate cross-check new posts.
I do trust all the ideas you’ve presented in your post. They are really convincing and will definitely work. Nonetheless, the posts are too short for newbies. May just you please lengthen them a bit from next time? Thank you for the post.
Wonderful web site. Lots of useful info here. I’m sending it to a few friends ans additionally sharing in delicious. And obviously, thanks to your effort!
Wow, superb blog layout! How long have you been blogging for? you make blogging look easy. The overall look of your site is magnificent, as well as the content!
hello!,I really like your writing so a lot! share we keep up a correspondence extra approximately your post on AOL? I need an expert in this house to unravel my problem. May be that is you! Taking a look ahead to see you.
of course like your website but you have to check the spelling on several of your posts. A number of them are rife with spelling issues and I in finding it very troublesome to inform the reality on the other hand I will certainly come back again.
My brother recommended I might like this web site. He was totally right. This post actually made my day. You cann’t imagine just how much time I had spent for this information! Thanks!
Thanks for sharing. I read many of your blog posts, cool, your blog is very good.
Temp Mail I like the efforts you have put in this, regards for all the great content.
Your point of view caught my eye and was very interesting. Thanks. I have a question for you.
La weekly Nice post. I learn something totally new and challenging on websites
Real Estate Pretty! This has been a really wonderful post. Many thanks for providing these details.
Real Estate For the reason that the admin of this site is working, no uncertainty very quickly it will be renowned, due to its quality contents.
Live Coin Watch Pretty! This has been a really wonderful post. Many thanks for providing these details.
Baddiehubs I really like reading through a post that can make men and women think. Also, thank you for allowing me to comment!
Lead Pipes in Iraq ElitePipe Factory is recognized as a trusted provider of lead pipes in Iraq. Despite the decline in use of lead pipes in modern applications due to health concerns, ElitePipe Factory maintains a legacy of supplying high-quality lead pipes for specific applications where they are still needed. Our commitment to reliability and excellence ensures that we offer products that meet the highest standards of performance. For information on our lead pipes and their applications, visit our website at ElitePipe Iraq.
Epoxy Resin Pipes in Iraq ElitePipe Factory stands at the forefront of producing epoxy resin pipes, known for their exceptional durability and resistance to corrosive substances. Our epoxy resin pipes are engineered to meet the rigorous demands of various industries, offering a high level of performance in applications such as water supply, sewage systems, and industrial fluid handling. The advanced technology and high-quality materials used in our manufacturing process ensure that our epoxy resin pipes deliver reliable and long-lasting service. ElitePipe Factory’s dedication to innovation and quality makes us one of the most trusted and reliable suppliers in Iraq. Discover more about our products at elitepipeiraq.com.
real estate shop You’re so awesome! I don’t believe I have read a single thing like that before. So great to find someone with some original thoughts on this topic. Really.. thank you for starting this up. This website is something that is needed on the internet, someone with a little originality!
GlobalBllog Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
Ezippi I truly appreciate your technique of writing a blog. I added it to my bookmark site list and will