Flutter_bloc + Freezed

Pawan Acharya
5 min readJul 5, 2021

Flutter_bloc
flutter_bloc is an easy yet powerful state management solution made to implement the BLoC (Business Logic Component) pattern.
It takes in the event (from UI) and returns a state as simple as that.

Freezed
If you are a native android developer using kotlin then you might be missing that clean data class, sealed class, copy with method, etc. For now, dart does not support this but here we have freezed package that helps you to do the same.
If you are not aware of sealed class then hold till the end and you will love it by then.

Why Freezed?
By default, dart performs referential equality. That means two objects of a class having the same values are not treated equal in dart.

So while using flutter_bloc even if the event emits the same old state then dart treats it as a new instance of state so the UI will rebuild again. This is not what we want.

To solve this we can override == and hashCode but why add boilerplate when we have a simple solution i.e freezed as it will automatically generate value equality so you can focus on your logic.

  • It will override toString and also you can use the copyWith method that will make your life easier.
  • You can use JSON serialization easily.
  • Union/sealed class will be there so you will never forget to handle some of your bloc states.

So basically you will get the benefits of multiple packages in this single package.

Project Setup

First, add all dependencies to pubspec.yaml file

Note this line somewhere as you will be needing this often
flutter pub run build_runner watch --delete-conflicting-outputs

Let's Start our Counter app

Folder Structure

We will be recreating the default flutter counter app using flutter_bloc and freezed package so let’s start by looking at the folder structure

I have created counter_cubit.dart and counter_state.dart using bloc plugin available in android studio.

Now we will create a state for our counter app.

@freezed: Use this annotation for the freezed class.

_$CounterState: This is the way we define a class in freezed. Yes, it seems weird but you will get used to it.

Regarding those factory methods inside CounterState class, this is the way we define sealed/union classes in freezed.

The right hand of the factory constructor (Initial, Loading, Loaded) can be private too. These names will come into the role a bit later in counter_cubit class to emit the state.

If you are still confused what is the need for these sealed/union classes then hold a bit, it will get clear when we write UI codes.

So now let’s look at counter_cubit.dart file

Here it has a simple method to increment the count. Just for a demo, I have used delay. And after delay new state is emitted.

Look at the state for eg. Initial(num:0) this came from the name you defined in the right hand of the factory constructor.
If you had defined that as private earlier then you had to access it using CounterState.initial(num: 0).

Now it's time to run build runner so our class can be generated.
flutter pub run build_runner watch --delete-conflicting-outputs

You can see counter_cubit.freezed.dart file has been generated.
You can peek inside and take a look(but don’t modify it manually). If you change the freezed class then a new code will auto-generated and if not then again run the build runner.

Building UI

Now the cubit part is over and let's learn how to handle UI with cubit and take advantage of Sealed/Union classes.

  1. Wrap MyApp() with BlocProvider
void main() {
runApp(BlocProvider(
create: (context) => CounterCubit(),
child: MyApp(),
));
}

2. Create stateless widget MyApp and stateful widget MyHomePage with a floating action button and text at the center.

3. Wrap Text widget with blocBuilder because it should be changed as the new state is emitted.

body: Center(
child: BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) {
return Text("1");
},
),
),

Now it’s the part where Sealed/Union class comes into play. With the sealed class, you can use className.when.
This when method will not let you miss any state to handle.
If you want to skip some state then you can use className.mayBeWhen.

This makes code a lot more clean and readable by replacing that if/else and switch.

body: Center(
child: BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) {
return state.when(
initial: (count) {
return Text(count.toString());
},
loading: () {
return const CircularProgressIndicator();
},
loaded: (count) {
return Text(count.toString());
},
);
},
),
),

Here the initial and loaded state will get a count as a parameter to display new/initial count.
The loading state will return CircularProgressIndicator.

4. Implement Floating Action Button

Create state variable

int currentCount = 0;

And for FAB

floatingActionButton: FloatingActionButton(
onPressed: () { BlocProvider.of<CounterCubit(context).incrementCount(currentCount); },

Also, update currentCount value in blocBuilder

body: Center(
child: BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) {
return state.when(
initial: (count) {
currentCount = count; ///Add this line
return Text(count.toString());
},
loading: () {
return const CircularProgressIndicator();
},
loaded: (count) {
currentCount = count; ///Add this line
return Text(count.toString());
},
);
},
),
),

5. Now rebuild the project and we are good to go.

Bonus

It’s easy to use json_serializable with freezed. So let’s see what changes we need to do.

part of 'counter_cubit.dart';

@freezed
class CounterState with _$CounterState {
const factory CounterState.initial({required int num}) = Initial;
const factory CounterState.loading() = Loading;
const factory CounterState.loaded(int num) = Loaded;

factory CounterState.fromJson(Map<String, dynamic> json) =>
_$CounterStateFromJson(json); //Add this line
}

Add factory fromJson inside counter_state.dart .

part 'counter_cubit.freezed.dart';
part 'counter_cubit.g.dart'; //add this line

part 'counter_state.dart';

class CounterCubit extends Cubit<CounterState> {
CounterCubit() : super(const Initial(num: 0));

Future<void> incrementCount(int current) async {
emit(const Loading());
await Future.delayed(const Duration(seconds: 2));
emit(Loaded(current + 1));
}
}

Add part file(counter_cubit.g.dart) in counter_cubit.dart file. This will generate file containing toJson() and fromJson() methods.

Don’t forget to run buildRunner if it has stopped already.

Now we can see counter_cubit.g.dart

So you can use
CounterState.fromJson(json) and state.toJson()

This toJson() and fromJson() method will make sense when you use with other model class.
Still, you got how to use it, don’t you?

Also, you can enjoy copyWith method.

So this is how you can use freezed and bloc.
For full code here is the link.
Thank you

--

--