Benefits of Using Freezed in BLoC Classes
If you’re building Flutter apps with the BLoC pattern, you’ve likely faced the challenge of managing immutable states and events. Enter Freezed—a code generation package that transforms how you write BLoC classes. Let’s explore why Freezed has become an essential tool for Flutter developers.
What is Freezed?
Freezed is a code generation library that creates immutable classes with minimal boilerplate. It automatically generates copyWith methods, equality comparisons, and sealed union types—all critical features for effective state management.
Have a look at freezed for more details.
Key Benefits in BLoC
1. Immutability Made Easy
BLoC architecture demands immutable states to ensure predictable state transitions. Freezed enforces this by default, preventing accidental mutations that could lead to hard-to-track bugs.
2. Effortless State Updates
The auto-generated copyWith() method makes creating new state instances incredibly simple. Instead of manually reconstructing entire objects, you modify only what’s changed:
emit(state.copyWith(status: AuthenticationStatus.authenticated));3. Type-Safe Event Handling
Freezed’s union types enable exhaustive pattern matching for events. The compiler ensures you handle every possible event type, catching errors at compile-time rather than runtime:
on<AuthenticationEvent>(
(event, emit) => switch (event) {
AuthCheck() => _authCheck(event, emit),
LoggedIn() => _onLoggedIn(event, emit),
LoggedOut() => _onLoggedOut(event, emit),
},
);4. Automatic Equality Comparison
Freezed implements value equality out of the box. This prevents unnecessary UI rebuilds when state values haven’t actually changed, optimizing your app’s performance.
5. Drastically Reduced Boilerplate
Without Freezed, you’d write hundreds of lines of repetitive code for constructors, equality operators, and toString methods. Freezed handles all this automatically, letting you focus on business logic.
Complete Implementation Example
Here’s how simple it is to define BLoC states and events with Freezed:
import 'package:freezed_annotation/freezed_annotation.dart';
part 'auth_state.freezed.dart';
@freezed
class AuthenticationState with _$AuthenticationState {
const factory AuthenticationState({
@Default(AuthenticationStatus.unknown) AuthenticationStatus status,
UserEntity? user,
String? errorMessage,
}) = _AuthenticationState;
}
@freezed
class AuthenticationEvent with _$AuthenticationEvent {
const factory AuthenticationEvent.authCheck() = AuthCheck;
const factory AuthenticationEvent.loggedIn(UserEntity user) = LoggedIn;
const factory AuthenticationEvent.loggedOut() = LoggedOut;
}Run dart run build_runner build --delete-conflicting-outputs and Freezed generates all the necessary code for you.
The BLoC Implementation
Here’s how you’d use these Freezed-generated classes in your actual BLoC:
import 'package:bloc/bloc.dart';
class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> {
AuthenticationBloc(this._userRepository)
: super(const AuthenticationState()) {
on<AuthenticationEvent>(
(event, emit) => switch (event) {
AuthCheck() => _onAuthCheck(event, emit),
LoggedIn() => _onLoggedIn(event, emit),
LoggedOut() => _onLoggedOut(event, emit),
},
);
}
final UserRepository _userRepository;
Future<void> _onAuthCheck(
AuthCheck event,
Emitter<AuthenticationState> emit,
) async {
emit(state.copyWith(status: AuthenticationStatus.loading));
final user = await _userRepository.getCurrentUser();
if (user != null) {
emit(state.copyWith(
status: AuthenticationStatus.authenticated,
user: user,
));
} else {
emit(state.copyWith(status: AuthenticationStatus.unauthenticated));
}
}
Future<void> _onLoggedIn(
LoggedIn event,
Emitter<AuthenticationState> emit,
) async {
emit(state.copyWith(
status: AuthenticationStatus.authenticated,
user: event.user,
));
}
Future<void> _onLoggedOut(
LoggedOut event,
Emitter<AuthenticationState> emit,
) async {
await _userRepository.signOut();
emit(state.copyWith(
status: AuthenticationStatus.unauthenticated,
user: null,
));
}
}Notice how clean the event handling is with pattern matching, and how easily we update state using copyWith. This is the power of Freezed in action.
Conclusion
Freezed transforms BLoC development from a boilerplate-heavy exercise into a clean, maintainable experience. By automating immutability, equality checks, and pattern matching, it eliminates entire categories of bugs while making your code more readable and type-safe. For any serious Flutter project using BLoC, Freezed isn’t just helpful—it’s essential. The time saved on boilerplate alone justifies its adoption, but the real value lies in the robust, error-resistant code it helps you create.