Immutable state: freezed, Equatable, == and rebuilds
Immutability is not an aesthetic choice in Flutter — it is the precondition for every optimisation mechanism. The reason is simple: Flutter and the state management libraries decide "what changed" using ==.
Each of these mechanisms depends on ==:
InheritedWidget.updateShouldNotify— whether dependents wake.ValueNotifier— itsvaluesetter compares with==.Selectorandcontext.selectin Provider — no rebuild when the selected value is unchanged.selectin Riverpod and the recomputation of providers.- Bloc's duplicate-state rule — no change occurs when
state == nextState. BlocSelector— the docs require the selected value to be immutable.
With a mutable object the same instance is modified: == sees no difference, the filter never fires, and you end up at one of two extremes — the UI never updates, or everything rebuilds all the time.
| Tool | What it gives | What to watch |
|---|---|---|
| Hand-written `==`/`hashCode` | No dependency | Easy to forget when a field is added |
| `package:equatable` | A short `==` via a `props` list | If a new field is missing from `props`, the comparison lies |
| `package:freezed` | `==`, `hashCode`, `copyWith` and sealed unions via code generation | `build_runner` build time; generated files must be managed in the repo |
| Dart `record`s | Value semantics out of the box, no code generation | No named type; not suited to large models |
An important update about freezed. The package's documentation now marks the when/map methods as legacy, explaining that as of Dart 3, Dart has built-in pattern matching using sealed classes, so you no longer need to rely on Freezed's generated methods. The recommended path is switch expressions and if-case statements.
copyWith, meanwhile, is still fully generated and works in a deep (nested) form as well.
Knowing this detail helps in an interview: code using when/map still works, but new code should prefer Dart 3 pattern matching.
// 1) Equatable: props siyahısı — yeni sahə əlavə edildikdə buraya da yazılmalıdır.
class CartState extends Equatable {
const CartState({this.items = const [], this.promo});
final List<Item> items;
final String? promo;
CartState copyWith({List<Item>? items, String? promo}) =>
CartState(items: items ?? this.items, promo: promo ?? this.promo);
@override
List<Object?> get props => [items, promo];
}
// 2) freezed: ==, hashCode, copyWith generasiya olunur; sealed union-lar dəstəklənir.
@freezed
sealed class CheckoutState with _$CheckoutState {
const factory CheckoutState.idle() = CheckoutIdle;
const factory CheckoutState.submitting() = CheckoutSubmitting;
const factory CheckoutState.success(Receipt receipt) = CheckoutSuccess;
const factory CheckoutState.failure(String message) = CheckoutFailure;
}
// Dart 3 pattern matching — freezed sənədinin tövsiyə etdiyi yol
// (when/map artıq legacy sayılır).
Widget build(BuildContext context) => switch (state) {
CheckoutIdle() => const CheckoutForm(),
CheckoutSubmitting() => const CircularProgressIndicator(),
CheckoutSuccess(:final receipt) => ReceiptView(receipt: receipt),
CheckoutFailure(:final message) => ErrorView(message: message),
};
// 3) record: kiçik, adsız dəyərlər üçün — kod generasiyası lazım deyil.
typedef SearchFilter = ({String query, int page});
final filter = (query: 'telefon', page: 1); // == dəyər üzrə işləyirImmutable state, three ways.
Immutable state has a real cost too: every change creates a new object. In large lists, writing [...items, item] on every emit means an O(n) copy. For small lists that is irrelevant; with thousands of items you need pagination, ListView.builder and a structure that limits the change surface (keeping the list as an id → item map, for instance). Noting that balance is more mature than "immutable is always better".
📚 Sources and documentation
- package:freezedofficialpub.dev
The legacy status of when/map and the Dart 3 pattern-matching recommendation are documented here.
- package:equatableofficialpub.dev
- package:json_serializableofficialpub.dev
Serialising immutable models — usually used together with freezed.
- Bloc: modeling stateofficialbloclibrary.dev
The source of the @immutable, copyWith, const and Equatable recommendations.