
Writing clean, immutable data classes in Dart can get repetitive fast. That’s where Freezed comes in—a powerful code generator that simplifies model creation, enables union/sealed classes, and eliminates boilerplate. In this guide, you’ll learn how to use Freezed to build better data models and manage state more effectively in your Flutter apps.
What Is Freezed?
Freezed is a Dart package that lets you create immutable classes, copy methods, deep equality, pattern matching, and sealed classes with almost no boilerplate.
It’s often used with state management tools like Riverpod, BLoC, and Cubit to manage model and state classes efficiently.
Why Use Freezed?
- Eliminates repetitive code (e.g.,
==
,hashCode
,copyWith
) - Enables union types (a.k.a. sealed classes)
- Makes your models testable and predictable
- Fully integrates with Dart’s null safety
- Works great with JSON serialization
Step 1: Install Freezed and Dependencies
Add the following to your pubspec.yaml
:
dependencies:
freezed_annotation: ^2.4.1
dev_dependencies:
build_runner: ^2.4.6
freezed: ^2.4.5
Then run:
flutter pub get
Step 2: Create a Basic Data Class with Freezed
Create a file called user_model.dart
:
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user_model.freezed.dart';
@freezed
class UserModel with _$UserModel {
const factory UserModel({
required String id,
required String name,
int? age,
}) = _UserModel;
}
Then run the generator:
flutter pub run build_runner build --delete-conflicting-outputs
Now you have an immutable class with:
- Deep equality
copyWith
- Readable
toString()
Step 3: Use copyWith
in Practice
final user = UserModel(id: '1', name: 'John');
final updatedUser = user.copyWith(name: 'Jane');
This creates a new instance with the updated value, without modifying the original—keeping your data flow safe and predictable.
Step 4: Create Union Types (Sealed Classes)
You can also use Freezed to create unions, perfect for representing different states.
Example: AuthState
@freezed
class AuthState with _$AuthState {
const factory AuthState.unauthenticated() = Unauthenticated;
const factory AuthState.authenticated(String userId) = Authenticated;
const factory AuthState.loading() = Loading;
}
Use it like this:
void handleState(AuthState state) {
state.when(
unauthenticated: () => print('Not logged in'),
authenticated: (id) => print('Logged in as $id'),
loading: () => print('Loading...'),
);
}
Bonus: Use maybeWhen
and map
Freezed gives you more advanced control for optional or partial matches:
state.maybeWhen(
authenticated: (id) => print('Hi $id'),
orElse: () => print('Default state'),
);
This keeps your logic super readable and safe.
When to Use Freezed
Use Freezed when:
- You want clean, immutable models
- You’re tired of writing
==
,hashCode
, andcopyWith
manually - You’re building complex state machines
- You’re working with APIs and need JSON serialization
Freezed supports integration with json_serializable
as well.
Conclusion
Freezed is one of the best tools in a Flutter developer’s toolkit. It removes repetitive code, introduces sealed classes, and makes your models easier to read, write, and maintain. Whether you’re working on a simple app or a large production project, using Freezed will keep your code clean and consistent.
Start small—build a model with Freezed today, and you’ll never want to go back.
Tһank you for the post on your blog. Do you
proѵide an RSS feed?