Why Bloc + Freezed is a match made in heaven
Tags: flutter, dart, bloc, freezed
Published: 2022-04-23 08:16:49

---
title: Why Bloc + Freezed is a match made in heaven
published: true
date: 2022-04-23 08:06:25 UTC
tags: flutter,dart,bloc,freezed
canonical_url: https://ptrbrynt.com/why-bloc-freezed-is-a-match-made-in-heaven
---

I'm a big fan of the [Bloc](https://bloclibrary.dev) library for Flutter apps, and when coupled with the [freezed](https://pub.dev/packages/freezed) package for creating immutable data classes and sealed unions, I think this pattern really shines.

## Sealed whats?

Let's start with some definitions. In other languages (like [Kotlin](https://kotlinlang.org/docs/sealed-classes.html)), there are patterns available such as union types, sealed classes, and pattern-matching.

These are all slightly different expressions of the same idea: that you can create a class with a fixed set of subclasses, linking together multiple otherwise separate types under one “umbrella” class.

Shapes are a good example of how this could be used. Here's an example in Kotlin:

```kotlin
sealed class Shape

data class Square(val sideLength: Int): Shape()

data class Rectangle(val length: Int, val width: Int): Shape()

data class Circle(radius: Int): Shape()

```

Here we are defining 3 classes – `Square`, `Rectangle`, and `Circle`. Each has its distinct properties, but we're able to have them all extend the `Shape` superclass.

More importantly, because `Shape` is a `sealed class`, no other subtypes can be defined outside this file; in other words, we can restrict the subtypes of `Shape` to the ones we define.

So why would this be helpful when using the Bloc pattern?

Let's consider a simple Counter bloc. It would have two Events: `CounterIncremented` and `CounterDecremented`. In pure Dart, we would have to do something like this:

```dart
abstract class CounterEvent {}

class CounterIncremented extends CounterEvent {
  CounterIncremented(this.incrementBy);
  
  final int incrementBy;
}

class CounterDecremented extends CounterEvent {
  CounterDecremented(this.decrementBy);
  
  final int decrementBy;
}

```

This is fine, but there are a couple of noteworthy issues:

- For Blocs with lots of events, you can see how this approach would become very verbose and cumbersome.
- The above snippet doesn't take equality operators into account; that's more boilerplate we'd need to add.
- When we eventually implement this Bloc, our code will be full of type-casting and type-checking, which is easy to make a mess with.

If we were writing Kotlin, we could solve these problems by implementing `CounterEvent` as a `sealed class` with each event type as a data class extending the base class.

```kotlin
sealed class CounterEvent

data class CounterIncremented(val incrementBy: Int) : CounterEvent()

data class CounterDecremented(val decrementBy: Int) : CounterEvent()

```

Kotlin's `data class` construct gives us immutability and equality without boilerplate.

But we're not writing Kotlin, and Dart doesn't have support for sealed classes out-of-the-box. We need to find an alternative!

## Freezed Events

The [freezed](https://pub.dev/packages/freezed) package gives us a couple of important abilities: we can create immutable data classes, and we can create sealed unions. Perfect!

Here's what our Counter events would look like using a freezed union:

```dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'counter_event.freezed.dart';

@freezed
class CounterEvent with _$CounterEvent {
	const factory CounterEvent.incremented(int incrementBy) = CounterIncremented;
	const factory CounterEvent.decremented(int decrementBy) = CounterDecremented;
}

```

With this implementation, freezed has given us two immutable event types with equality operators. It even generates bonus stuff like `copyWith` and `toString` methods. This is a totally battle-ready implementation of our `CounterEvent` type.

When it comes to actually using instances of `CounterEvent`, freezed also gives us access to some [pattern-matching](https://en.wikipedia.org/wiki/Pattern_matching) syntax. Here's how we could implement our event handler:

```dart
event.when(
	incremented: (incrementBy) => emit(state + incrementBy),
	decremented: (decrementBy) => emit(state - decrementBy),
);

```

This syntax is helpful for a couple of reasons. First, forces us to account for every possibility; in pure Dart with type-casting, we could easily forget about or ignore certain event types. Second, it's super easy to read and understand!

## Freezed States

Let's look at something more complex: writing a state type using freezed. Let's say we have some data to load: we'll probably want an initial state, an error state, and a success state. We also need a loading indicator to show the user when the data is being refreshed.

Using freezed, we can implement a state class like this:

```dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'data_state.freezed.dart';

@freezed
class DataState with _$DataState {
	const factory DataState.initial(bool isLoading) = DataInitial;
	const factory DataState.error(String message, bool isLoading) = DataError;
	const factory DataState.success(Data data, bool isLoading) = DataSuccess;
}

```

Then, we can implement a `BlocBuilder` like this:

```dart
BlocBuilder<DataBloc, DataState>(
	builder: (context, state) {
		return Column(
			children: [
				if (state.isLoading) _buildProgressIndicator(),
				state.when(
					initial: (_) => _buildInitial(),
					error: (message, _) => _buildError(message),
					success: (data, _) => _buildSuccess(data),
				),
			],
		);
	},
);

```

There are a couple of things to notice here:

- Because every state type has an `isLoading` property, we can access it without knowing what subtype of state we have. Thanks, freezed!
- Again, the pattern-matching syntax provided by freezed is extremely useful here in giving us some super-readable code which forces us to handle all possible states.

## Is this the perfect solution?

No.

There is no perfect solution! It could very easily be argued that freezed classes are still pretty verbose, and some developers prefer to not rely on code generation in their apps. And some folks just don't like Bloc at all for some reason. That's all fine – do what works for you!

But, having worked on more than 10 new Flutter projects in the past year or so, I've found that this pattern allows us to build and iterate quickly on our code, and improves my team's ability to understand one another's work easily.

As with lots of software engineering patterns and practices, there's no single right way to do things; the most important thing you can do is to pick one pattern and stick to it consistently.

* * *

Liked this? [Buy me a coffee](https://buymeacoffee/ptrbrynt).

Find me elsewhere online [here](https://linktr.ee/ptrbrynt).