What is Freezed?
A powerful code‑generation package that removes boilerplate from your Dart/Flutter models. With Freezed, you can write immutable data classes effortlessly while enjoying built‑in JSON (de)serialization, pattern matching, and sealed unions.
Key Features:
🔒 Immutable Data Classes
Guaranteed immutability
Built‑in copyWith
Automatic ==, hashCode, and toString
🔄 JSON Serialization
Seamless encoding/decoding
Custom serialization hooks
Nested object support
🎯 Union Types / Sealed Classes
Type‑safe pattern matching
Exhaustive switch handling
Discriminated unions
🛠️ Generated Utilities
Deep copy
Equality checks
toString methods
Builder patterns
⚡ Zero Boilerplate
Minimal handwritten code
Maximum type safety
When to Use Freezed?
Ideal for projects requiring complex data models, API integration, state/event handling, error unions, or clean‑architecture layers.
Add to your pubspec.yaml:
Run: flutter pub get
Here’s a realistic e‑commerce example in product_model.dart:
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 build_runner to generate the .freezed.dart and .g.dart files:
# Generate once
flutter pub run build_runner build --delete-conflicting-outputs
# Or watch for changes
flutter pub run build_runner watch
void main() {
final jsonString = '''
{
"status": "success",
"products": [
{
"id": "prod_123",
"name": "Premium Headphones",
"price": 199.99,
"description": "High-quality wireless headphones",
"categories": ["electronics", "audio"],
"inventory": {
"quantity": 50,
"warehouse": "CENTRAL",
"status": "IN_STOCK"
},
"image_urls": [
"https://example.com/headphones1.jpg",
"https://example.com/headphones2.jpg"
],
"isFeatured": true,
"lastUpdated": "2024-03-15T14:30:00.000Z"
}
],
"metadata": {
"totalCount": 100,
"page": 1,
"items_per_page": 20
}
}
''';
final responseMap = jsonDecode(jsonString);
final productResponse = ProductResponse.fromJson(responseMap);
final firstProduct = productResponse.products.first;
print('Product name: ${firstProduct.name}');
print('Price: \$${firstProduct.price}');
final updatedProduct = firstProduct.copyWith(
price: 179.99,
inventory: firstProduct.inventory.copyWith(quantity: 45),
);
final jsonOutput = jsonEncode(productResponse.toJson());
print(jsonOutput);
}
5.2 Custom Serialization
@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: Clear class names; suffix models with Model if helpful.
Organization:
lib/
└── models/
└── product/
├── product_model.dart
├── product_model.freezed.dart
└── product_model.g.dart
flutter pub run build_runner clean
flutter pub run build_runner build --delete-conflicting-outputs
JSON Errors: Ensure nested classes have fromJson/toJson.
Type Mismatches: Use custom fromJson converters:
@JsonKey(fromJson: _parseDate)
final DateTime date;
static DateTime _parseDate(dynamic json) {
if (json is String) return DateTime.parse(json);
throw FormatException('Invalid date format');
}
Missing Part Files: Verify part 'xyz.freezed.dart'; and part 'xyz.g.dart';.
By following this guide, you’ll harness the full power of Freezed to keep your Flutter codebase clean, type‑safe, and free of boilerplate. Happy coding!