What is Freezed?
Freezed is a code-generation package that eliminates tedious boilerplate from your Dart/Flutter models. It makes it effortless to define:
🔒 Immutable data classes with built-in copyWith, ==, hashCode, and toString.
🔄 JSON serialization (nested objects, custom hooks).
🎯 Union types / sealed classes for type-safe pattern matching.
🛠️ Helper utilities like deep copies and builder patterns—all with ⚡ zero boilerplate.
Use Freezed when you need complex data models, API integration, robust state or event handling, or clean-architecture layers.
Add to pubspec.yaml:
Fetch packages:
flutter pub get
Create product_model.dart for an e-commerce example:
import 'package:freezed_annotation/freezed_annotation.dart';
part 'product_model.freezed.dart';
part 'product_model.g.dart';
@freezed
class ProductResponse with _$ProductResponse {
const factory ProductResponse({
required String status,
required List<Product> products,
required Metadata metadata,
}) = _ProductResponse;
factory ProductResponse.fromJson(Map<String, dynamic> json) =>
_$ProductResponseFromJson(json);
}
@freezed
class Product with _$Product {
const factory Product({
required String id,
required String name,
required double price,
required String description,
required List<String> categories,
required ProductInventory inventory,
@JsonKey(name: 'image_urls') required List<String> imageUrls,
@Default(false) bool isFeatured,
DateTime? lastUpdated,
}) = _Product;
factory Product.fromJson(Map<String, dynamic> json) =>
_$ProductFromJson(json);
}
@freezed
class ProductInventory with _$ProductInventory {
const factory ProductInventory({
required int quantity,
required String warehouse,
@Default('IN_STOCK') String status,
}) = _ProductInventory;
factory ProductInventory.fromJson(Map<String, dynamic> json) =>
_$ProductInventoryFromJson(json);
}
@freezed
class Metadata with _$Metadata {
const factory Metadata({
required int totalCount,
required int page,
@JsonKey(name: 'items_per_page') required int itemsPerPage,
}) = _Metadata;
factory Metadata.fromJson(Map<String, dynamic> json) =>
_$MetadataFromJson(json);
}
Run the builder:
# One-time generate
flutter pub run build_runner build --delete-conflicting-outputs
# Or watch for changes
flutter pub run build_runner watch
This creates the .freezed.dart and .g.dart files automatically.
5.1 Using Generated Classes
void main() {
final jsonString = '''{ ... }'''; // your JSON here
final Map<String, dynamic> map = jsonDecode(jsonString);
final response = ProductResponse.fromJson(map);
// Access data
final p = response.products.first;
print(p.name);
// copyWith example
final updated = p.copyWith(price: p.price * 0.9);
// Back to JSON
final output = jsonEncode(response.toJson());
print(output);
}
5.2 Custom Serialization with @JsonKey
@freezed
class Product with _$Product {
const factory Product({
required String id,
@JsonKey(name: 'product_name') required String name,
@JsonKey(fromJson: priceFromJson, toJson: priceToJson)
required double price,
}) = _Product;
static double _priceFromJson(dynamic json) => (json as num).toDouble();
static num _priceToJson(double price) => price;
factory Product.fromJson(Map<String, dynamic> json) =>
_$ProductFromJson(json);
}
Naming: Use clear, descriptive class names; suffix with Model if helpful.
Organization:
lib/
└── models/
└── product/
├── product_model.dart
├── product_model.freezed.dart
└── product_model.g.dart
Defaults & Nullability: Use @Default() for defaults; consider nullable types for optional fields.
Documentation: Add comments and examples for complex fields and union cases.
Build conflicts:
flutter pub run build_runner clean
flutter pub run build_runner build --delete-conflicting-outputs
Serialization issues: Ensure all nested types have fromJson/toJson.
Type errors: Use custom fromJson converters to handle mismatches.
Missing part files: Verify your part 'xxx.freezed.dart'; and part 'xxx.g.dart'; statements.
By following this guide, you’ll harness Freezed’s power to maintain a clean, type-safe, and boilerplate-free codebase in your Flutter projects. Happy coding!