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.

Bloc and Cubit use a 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.

The lack of events creates a far-reaching difference. While it simplifies code quite a bit, you lose the ability to easily track incoming events (a.k.a. changes). If you'd like to perform event sourcing, choose Bloc over Cubit.

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.

Always override equality of the state classes. Bloc will not emit two states which are equal after one another.

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 SnackBars 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);
}
Yes, it is extra boilerplate and we haven't even gotten to the 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 😎

About the author 

Matt Rešetár

Matt is an app developer with a knack for teaching others. Working as a Flutter freelancer and most importantly developer educator, he doesn't have a lot of free time 😅 Yet he still manages to squeeze in tough workouts 💪

You may also like

Flutter Riverpod Tutorial – The Better Provider

Flutter Custom & Staggered Page Transition Animation Tutorial

  • It is the tutorial I was waiting since the introduction of BLoC 6.0.0. It cannot be better explained. Good job!

    • 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()

  • 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.

  • 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

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >