dart-flutter-equatable-engineering
Skill8 starsChanged 48 days agoMITView on GitHub
Design, implement, review, debug, test, migrate, and optimize value equality in production Dart and Flutter code using Equatable or appropriate alternatives. Use this skill whenever work involves Equatable, `props`, immutable Bloc/Cubit states or events, equality bugs, suppressed state emissions, lists/sets/maps in equality, Map keys or Set elements, `hashCode`, `stringify`, entity-vs-value semantics, records/generated equality, or migrating manual equality—even if the user only says two objects compare incorrectly or the UI did not rebuild.
In this repository70 other files it gives its agents.
- camerawesome-flutterSkill
- dart-args-production-cliSkill
- dart-flutter-ffi-systems-engineeringSkill
- dart-metaSkill
- document-camera-frameSkill
- document-scan-flutterSkill
- figma-to-flutter-expertSkill
- flutter-animations-production-engineeringSkill
- flutter-app-hardening-reverse-engineeringSkill
- flutter-app-icon-engineeringSkill
- flutter-application-security-engineeringSkill
- flutter-app-links-deep-link-engineeringSkill
- flutter-cachingSkill
- flutter-concurrency-memory-performanceSkill
- flutter-confetti-engineeringSkill
- flutter-dart-package-engineeringSkill
- flutter-design-system-from-design-mdSkill
- flutter-dio-cache-interceptor-engineeringSkill
- flutter-dio-engineeringSkill
- flutter-doc-scannerSkill
- flutter-drift-persistence-engineeringSkill
- flutter-expert-tipsSkill
- flutter-firebase-analyticsSkill
- flutter-firebase-core-engineeringSkill
- flutter-firebase-messagingSkill
- flutter-get-it-injectable-dependency-injectionSkill
- flutter-go-router-engineeringSkill
- flutter-hive-persistence-engineeringSkill
- flutter-image-performanceSkill
- flutter-image-picker-production-engineeringSkill
- flutter-lints-static-analysis-engineeringSkill
- flutter-local-notifications-production-engineeringSkill
- flutter-memory-leak-engineeringSkill
- flutter-mobile-developmentSkill
- flutter-motion-engineeringSkill
- flutter-multi-environmentSkill
- flutter-native-splash-engineeringSkill
- flutter-package-plugin-selectionSkill
- flutter-path-provider-production-storageSkill
- flutter-performance-jank-optimizationSkill
- flutter-permission-handler-engineeringSkill
- flutter-pinputSkill
- flutter-production-build-releaseSkill
- flutter-production-engineeringSkill
- flutter-production-loggingSkill
- flutter-pro-image-editor-engineeringSkill
- flutter-secure-storage-security-engineeringSkill
- flutter-sentry-engineeringSkill
- flutter-share-plus-engineeringSkill
- flutter-skeletonizer-engineeringSkill
- flutter-squadron-concurrency-engineeringSkill
- flutter-supabase-engineeringSkill
- flutter-svg-engineeringSkill
- flutter-ui-performance-engineeringSkill
- flutter-url-launcher-engineeringSkill
- flutter-video-player-production-engineeringSkill
- flutter-webview-production-engineeringSkill
- freezed-production-engineeringSkill
- html-to-flutterSkill
- hydrated-blocSkill
- The first 60 shown.
The file
Show its rules and text
We split this skill into the rules it gives an agent
55 rules. About 0 are common, 41 are distinctive, and 14 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈41
Distinctive · estimate
This author's own choices. Worth a look.
14
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 8
Treat Equatable as an implementation mechanism for an already-defined equality model.
- Very specific: line 10
Do not begin with extends Equatable.
- Distinctive: line 21
Before changing repository code:
- Distinctive: line 26
verify version-sensitive APIs before migrating legacy code.
- Distinctive: line 34
Do not silently project these details onto a repository using materially different versions.
- Distinctive: line 43
Read the full relevant reference before making a high-risk semantic change.
- Distinctive: line 46
Use value equality only when the type represents a logical value or immutable snapshot whose values should be interchangeable.
- Distinctive: line 47
Keep all equality-relevant state immutable for the object's lifetime.
- Distinctive: line 50
Verify nested values' own equality semantics.
- Very specific: line 51
Treat mutable collections inside props as correctness hazards.
- Distinctive: line 53
Treat Map keys and Set elements as deeply immutable with respect to equality.
- Distinctive: line 54
Use one authoritative equality implementation per type.
- Distinctive: line 55
Do not mutate Bloc/Cubit state and re-emit the same object.
- Very specific: line 56
Treat changes to props as behavioral changes requiring tests.
- Distinctive: line 57
Do not use equality as a substitute for event concurrency, debounce, retry, or deduplication policy.
- Very specific: line 58
Never expose secrets or sensitive personal data through stringify, toString, observers, logs, analytics, or crash tooling.
- Distinctive: line 87
Search for where the type is compared or hashed:
- Distinctive: line 101
If equality has no consumer and no clear semantic value, do not add machinery by default.
- Very specific: line 109
default identity equality for lifecycle/resource/service objects.
- Distinctive: line 110
Do not combine Equatable with another generated or manual equality system merely for convenience.
- Very specific: line 112
Do not introduce new EquatableMixin usage on the 2.1.0 baseline.
- Distinctive: line 116
If yes, include it.
- Distinctive: line 118
If no, document why it is outside equality.
- Very specific: line 134
Use List<Object?> when nullable equality fields exist.
- Very specific: line 136
Keep props deterministic and stable.
- Distinctive: line 140
When mutable data enters a value object/state:
- Distinctive: line 143
ensure nested equality objects are also immutable.
- Distinctive: line 144
create a new snapshot for every semantic state change.
- Very specific: line 153
Never mutate equality-relevant state after inserting an object into a HashSet or using it as a HashMap key.
- Very specific: line 166
Do not infer that a plain class becomes structurally equal simply because it appears inside a list or map contained in props.
- Distinctive: line 172
When subclass equality extends parent semantics, include the parent values intentionally:
- Very specific: line 180
Do not spread super.props automatically if the subclass intentionally defines a different equality contract.
- Distinctive: line 186
keep nested values and collections immutable.
- Distinctive: line 190
test every important transition.
- Distinctive: line 204
When a UI does not update, inspect in this order:
- Distinctive: line 213
Do not claim Equatable deduplicates Bloc events.
- Distinctive: line 218
leave current value unchanged.
- Very specific: line 220
If that distinction matters, use a sentinel, wrapper, generated copyWith, or another explicit representation.
- Distinctive: line 225
do not persist or externally depend on Equatable's numeric hash algorithm.
- Distinctive: line 226
test lookup with a separately constructed equal key when behavior matters.
- Distinctive: line 227
If a key needs mutable lifecycle state, redesign the key as an immutable value.
- Distinctive: line 234
default behavior differs between debug and release according to current source.
- Distinctive: line 240
Never expose passwords, access tokens, refresh tokens, private keys, payment secrets, or unnecessary personal information through automatic …
- Distinctive: line 242
Do not optimize semantically correct small models based on folklore.
- Distinctive: line 259
Prefer architectural improvements such as:
- Distinctive: line 265
Do not replace full semantic equality with a revision number unless the architecture proves:
- Distinctive: line 287
When equality is wrong:
- Distinctive: line 294
check hash-key/set use;
- Very specific: line 297
add a characterization test reproducing the issue.
- Very specific: line 298
Do not change props until the intended semantics are explicit.
- Distinctive: line 317
every data class extends Equatable automatically.
- Distinctive: line 331
When reviewing existing code:
- Distinctive: line 346
Do not rewrite unrelated application architecture while fixing equality.
- Distinctive: line 369
all independent equality-defining fields participate.
- Distinctive: line 386
Do not ask only:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Dart & Flutter Equatable Engineering
Goal
Treat Equatable as an implementation mechanism for an already-defined equality model.
Do not begin with extends Equatable. First determine:
- what the type represents;
- whether separately constructed instances should be interchangeable;
- which fields define that logical value;
- whether equality must remain stable for the object's lifetime;
- whether Bloc, selectors, caches, sets, maps, or tests rely on that equality;
- which single equality mechanism best represents the semantics. Optimize for correctness first. Equality mistakes can silently alter state emission, rebuild behavior, cache keys, map/set lookup, deduplication, and tests.
Research baseline and freshness guard
The bundled source is based on Equatable 2.1.0 behavior, Dart equality documentation, and current Bloc/flutter_bloc behavior from that research baseline.
Before changing repository code:
- inspect
pubspec.yamlandpubspec.lock; - identify the Equatable version;
- identify Dart/Flutter constraints;
- identify Bloc/flutter_bloc versions when state equality is involved;
- verify version-sensitive APIs before migrating legacy code. Important baseline facts from the supplied research:
- Equatable 2.1.0 requires Dart
>=3.0.0 <4.0.0; Equatableis anabstract mixin classin that version;- legacy
EquatableMixinis deprecated in favor ofwith Equatable; - Equatable equality includes
runtimeTypeandprops; - current Equatable structurally compares supported lists/iterables, sets, maps, and nested values;
- current Bloc/Cubit suppresses later emissions when the next state equals the current state, with the documented first-emission exception. Do not silently project these details onto a repository using materially different versions.
Reference routing
Load only the reference relevant to the task.
| Task | Read |
Choosing equality semantics; Equatable API; props; nested objects; collections; inheritance; entities; records; generators; manual equality | references/01-equality-modeling.md |
Bloc/Cubit states/events; copyWith; selectors; hash collections; stringify; sensitive output | references/02-bloc-state-and-stringify.md |
| Equality performance; large collections; profiling; tests; debugging wrong equality | references/03-performance-testing-debugging.md |
| Anti-pattern review; new-model workflow; existing-code review; migrations; production checklists | references/04-antipatterns-workflows-checklists.md |
| Read the full relevant reference before making a high-risk semantic change. |
Non-negotiable invariants
- Define equality semantics before choosing Equatable.
- Use value equality only when the type represents a logical value or immutable snapshot whose values should be interchangeable.
- Keep all equality-relevant state immutable for the object's lifetime.
- Include every logically significant independent value in
props. - Exclude a field only because the domain says it is outside equality—not to force fewer rebuilds.
- Verify nested values' own equality semantics.
- Treat mutable collections inside
propsas correctness hazards. - Preserve
a == b => a.hashCode == b.hashCode. - Treat Map keys and Set elements as deeply immutable with respect to equality.
- Use one authoritative equality implementation per type.
- Do not mutate Bloc/Cubit state and re-emit the same object.
- Treat changes to
propsas behavioral changes requiring tests. - Do not use equality as a substitute for event concurrency, debounce, retry, or deduplication policy.
- Never expose secrets or sensitive personal data through
stringify,toString, observers, logs, analytics, or crash tooling. - Optimize equality only after profiling identifies a real cost.
Step 1 — Classify the type
Classify the type before editing it.
Typical categories:
- value object;
- entity identity;
- immutable entity snapshot;
- Bloc/Cubit state;
- Bloc event;
- DTO;
- UI/view model;
- configuration value;
- service/repository;
- controller/resource/lifecycle object;
- short tuple-like internal result. Default decisions:
- value object: value equality is usually appropriate;
- immutable state snapshot: value equality is often appropriate;
- service/repository/controller/resource: usually retain identity semantics;
- DTO used only for decode-map-discard: equality may be unnecessary;
- generated immutable model: keep generated equality rather than layering Equatable;
- short internal tuple: consider a Dart record;
- approximate numeric comparison: model explicitly rather than expecting Equatable to provide tolerance. State the intended contract in one sentence before implementation, for example:
Two
Moneyvalues are equal when minor-unit amount and currency match. For an entity, distinguish identity semantics from snapshot semantics.User(id)andUserSnapshot(id, name, email)can legitimately have different equality contracts.
Step 2 — Inspect equality consumers
Search for where the type is compared or hashed:
- direct
==/!=; - unit tests;
- Bloc/Cubit
emit; buildWhen/listenWhen;BlocSelector/context.select;Setmembership;Mapkeys;- caches;
- memoization;
- deduplication;
- collections using equality;
- test matchers;
- logging/stringification. If equality has no consumer and no clear semantic value, do not add machinery by default.
Step 3 — Choose one equality mechanism
Choose exactly one authoritative implementation:
extends Equatablewhen the type is a value and no other superclass is required;with Equatablewhen another superclass is required and the repository's version supports it;- Dart record for compact tuple-like values;
- generated equality when the project's model generator already owns
==andhashCode; - manual equality for specialized semantics Equatable does not express cleanly;
- default identity equality for lifecycle/resource/service objects. Do not combine Equatable with another generated or manual equality system merely for convenience.
Do not introduce new EquatableMixin usage on the 2.1.0 baseline.
Step 4 — Design props from semantics
For every field ask:
If only this field changes, should the object become a different logical value? If yes, include it.
If no, document why it is outside equality.
Usually include:
- scalar values defining the value;
- state/status enums;
- domain IDs when identity participates;
- nested immutable values;
- nullable logical values;
- semantically relevant collections. Usually exclude only when semantically irrelevant:
- caches derived fully from included fields;
- services/repositories/loggers;
- controllers/subscriptions/resources;
- callbacks when callback identity is not part of the logical value;
- diagnostic-only metadata;
- redundant pure derived fields.
Use
List<Object?>when nullable equality fields exist.
Keep props deterministic and stable.
Step 5 — Guarantee immutability
final List<T> does not make the list contents immutable.
When mutable data enters a value object/state:
- defensively copy where external aliases can mutate it;
- expose unmodifiable collections when appropriate;
- ensure nested equality objects are also immutable;
- create a new snapshot for every semantic state change. Examples of safe boundaries:
items = List.unmodifiable(items);
roles = Set.unmodifiable(roles);
values = Map.unmodifiable(values);An unmodifiable outer collection does not freeze mutable objects contained inside it. Copy as deeply as the mutation boundaries require.
Never mutate equality-relevant state after inserting an object into a HashSet or using it as a HashMap key.
Step 6 — Verify nested and collection semantics
Equatable cannot manufacture value equality for arbitrary nested classes.
For every nested object in props, inspect its == and hashCode behavior.
On the supplied 2.1.0 baseline:
- ordered iterables/lists are compared structurally and order matters;
- sets are compared structurally without relying on insertion order;
- maps compare corresponding keys and recursively compared values;
- supported nested collections recurse through those rules;
- nested Equatable values use their own equality;
- ordinary nested objects still use whatever equality they implement.
Do not infer that a plain class becomes structurally equal simply because it appears inside a list or map contained in
props.
Step 7 — Handle inheritance deliberately
Equatable requires matching runtime types after the identical-instance fast path.
Therefore two subclasses with identical empty props remain unequal if their runtime types differ.
When subclass equality extends parent semantics, include the parent values intentionally:
@override
List<Object?> get props => [
...super.props,
childValue,
];Do not spread super.props automatically if the subclass intentionally defines a different equality contract. Review the hierarchy first.
Step 8 — Apply Bloc/Cubit rules
Bloc/Cubit state equality is operational, not cosmetic.
For each state:
- include every logical state field;
- keep nested values and collections immutable;
- make
copyWithreturn a new snapshot; - support explicit null-clearing when nullable state requires it;
- preserve meaningful status/loading/error fields in equality;
- test every important transition. Never do:
state.items.add(item);
emit(state);Prefer:
emit(
state.copyWith(
items: [...state.items, item],
),
);When a UI does not update, inspect in this order:
- did Bloc/Cubit accept the new state;
- does
state == nextStatereturn true; - is a logical field missing from
props; - was the existing state/nested collection mutated;
- is
buildWhensuppressing the rebuild; - does
BlocSelector/context.selectreturn an equal selected value; - is the widget observing the intended Bloc instance;
- is another layer caching/filtering the value. Do not claim Equatable deduplicates Bloc events. Event concurrency/transformation policy is separate.
Step 9 — Review copyWith
A copyWith should preserve unchanged fields and create a new immutable snapshot.
For nullable properties, detect whether the API must distinguish:
- leave current value unchanged;
- explicitly set value to
null. If that distinction matters, use a sentinel, wrapper, generatedcopyWith, or another explicit representation. Do not silently make null impossible to set.
Step 10 — Protect hash semantics
For objects used as Map keys or Set elements:
- equality-relevant data must never mutate while stored;
- equal instances must produce equal hash codes;
- do not persist or externally depend on Equatable's numeric hash algorithm;
- test lookup with a separately constructed equal key when behavior matters. If a key needs mutable lifecycle state, redesign the key as an immutable value.
Step 11 — Control stringification and sensitive output
stringify affects toString, not equality.
On the supplied baseline:
- per-class
stringifycan override global behavior; EquatableConfig.stringifyis global mutable configuration;- default behavior differs between debug and release according to current source. For secret-bearing or privacy-sensitive models:
- set
stringifyto false where appropriate; - provide a redacted
toStringonly if useful; - inspect Bloc observers, logs, analytics, crash reports, and test snapshots;
- restore
EquatableConfig.stringifyafter tests that mutate it. Never expose passwords, access tokens, refresh tokens, private keys, payment secrets, or unnecessary personal information through automatic property output.
Step 12 — Performance discipline
Do not optimize semantically correct small models based on folklore.
Equality cost can become meaningful for:
- thousands of list/set/map elements;
- deeply nested structures;
- repeated equality in hot state paths;
- repeated hash computation;
- large hash collections. Profile before redesigning.
Measure:
- equality call frequency;
hashCodecall frequency;- collection sizes;
- nested depth;
- state-selection comparison frequency;
- actual CPU/allocation cost. Prefer architectural improvements such as:
- pagination;
- normalized state;
- smaller selected view models;
- splitting independent state;
- immutable/persistent data structures when justified. Do not replace full semantic equality with a revision number unless the architecture proves:
same revision ⇔ same logical valueand guarantees every semantic change updates that revision correctly.
Step 13 — Test equality behavior
For important value types, test behavior rather than the literal props list.
Cover:
- separately constructed equal values;
- equal values have equal hash codes;
- each equality-defining field varied independently;
- nullable fields;
- nested values;
- list order;
- set/map semantics where relied upon;
- subtype behavior;
- hash-map/set lookup;
- Bloc state transitions;
- sensitive stringification where relevant. For high-risk models, create a props regression matrix: hold all fields constant and change one logical field per test. This catches accidental omissions while preserving implementation flexibility.
Step 14 — Debug equality systematically
When equality is wrong:
- compare
runtimeType; - inspect both
propsvalues; - identify omitted or accidental fields;
- compare each nested object's equality directly;
- inspect list order, set members, map keys/values, and nested collections;
- search mutation paths;
- check hash-key/set use;
- compare current and next Bloc state directly;
- inspect selectors/build filters;
- add a characterization test reproducing the issue.
Do not change
propsuntil the intended semantics are explicit.
Step 15 — Refactor and migrate safely
Changing props, equality technology, or mutability can change application behavior without changing the public field list.
Before migration:
- characterize current equality with tests;
- record runtime-type behavior;
- record nested/collection semantics;
- inspect Set/Map usage;
- inspect Bloc emission/selectors;
- inspect stringification/logging.
Manual equality → Equatable
Preserve existing semantics first. Migrate only if Equatable can represent the same contract. Remove manual ==/hashCode after tests prove equivalence.
Equatable → generated model/record/manual equality
Compare behavior before removing Equatable. Different “value equality” mechanisms can differ in runtime-type, nested collection, and hashing semantics.
Legacy EquatableMixin
On repositories using Equatable 2.1.0-compatible APIs, migrate toward with Equatable when appropriate and covered by tests.
Common review findings to reject
Reject these patterns unless a documented domain invariant proves them correct:
- every data class extends Equatable automatically;
- services/controllers/resources use value equality merely because they have fields;
- logical state fields omitted from
propsto reduce rebuilds; - mutable list/map/set stored directly in
props; - state mutated and re-emitted by reference;
- timestamps/random IDs added solely to force inequality;
- callback identity placed in equality without semantic reason;
- generated equality combined with Equatable;
- manual
operator ==layered on top of Equatable; - entity equality chosen mechanically as “all fields” or “ID only” without domain reasoning;
- revision-only equality introduced solely because collections are large;
stringifyenabled on credentials or sensitive personal models;- Equatable event equality described as Bloc deduplication/concurrency control.
Repository review workflow
When reviewing existing code:
- inventory Equatable types and manual/generated equality;
- classify each type's semantics;
- identify equality consumers;
- inspect
propsand excluded fields; - inspect nested equality;
- inspect collection ownership and mutation;
- inspect inheritance and
super.props; - inspect Bloc states, events, selectors, and transitions;
- inspect hash-key/set use;
- inspect
stringifyand logging exposure; - inspect equality tests;
- profile only where cost is suspected;
- make the smallest semantics-preserving fix;
- rerun affected tests and repository checks. Do not rewrite unrelated application architecture while fixing equality.
Change report format
For a meaningful equality change, report:
Type and role
- value object / entity / state / event / DTO / resource / other. Intended equality
- plain-language statement of when two instances are equal. Existing issue
- missing field, mutation, wrong nested equality, duplicate implementation, hash instability, state suppression, sensitive stringify, or measured performance cost. Change
- exact equality/immutability/migration modification. Behavioral impact
- Bloc emission, selector behavior, Set/Map lookup, cache/deduplication, tests, or logging affected. Verification
- tests run and relevant equality/hash/state assertions. Residual risk
- unverified versions, callers, or performance assumptions.
Production definition of done
An important equality implementation is ready when:
- the type's equality semantics are intentional;
- value equality is appropriate for the type;
- one equality mechanism owns
==andhashCode; - all independent equality-defining fields participate;
- excluded fields are intentionally outside equality;
- equality-relevant state is immutable;
- external mutable inputs are defensively handled;
- nested equality has been verified;
- collection semantics match the domain;
- hash code remains stable for the object's lifetime;
- Set/Map key behavior is safe where used;
- inheritance/runtime-type behavior is understood;
- Bloc/Cubit states create immutable snapshots;
- intended state transitions do not collapse accidentally;
- selectors/build filters have been considered;
- sensitive values cannot leak through stringification;
- important equality behavior has regression tests;
- performance changes, if any, are profiling-driven;
- repository checks pass.
Final operating rule
Do not ask only:
How do I add Equatable to this class? Resolve instead:
What does the type represent?
→ Should two distinct instances be interchangeable?
→ Which values define that sameness?
→ Can those values remain immutable?
→ What do nested values mean by equality?
→ Will Bloc/selectors/hash collections rely on it?
→ Which one equality mechanism best expresses the contract?
→ Can tests prove the behavior?Then implement the smallest equality design that preserves those guarantees.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

