{"slug":"dart-3-updates","title":"dart-3-updates","summary":"Use when writing switch statements, refactoring if-else chains, creating data classes, choosing records vs classes, destructuring values, or modernizing pre-Dart-3 code.","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-08-30T09:57:16.010242Z","repo":{"url":"https://github.com/evanca/flutter-ai-rules","stars":644,"forks":67,"license":"MIT","updatedAt":"2026-09-14T07:51:38Z"},"bodyHtml":"<hr>\n<h2>name: dart-3-updates\ndescription: \"Use when writing switch statements, refactoring if-else chains, creating data classes, choosing records vs classes, destructuring values, or modernizing pre-Dart-3 code.\"\nlicense: MIT</h2>\n<h1>Dart 3 Updates Skill</h1>\n<p>Apply Dart 3 language features — branches, patterns, pattern types, and records — correctly and idiomatically.</p>\n<h2>When to Use</h2>\n<p>Use this skill when:</p>\n<ul>\n<li>Writing or refactoring <code>switch</code> statements or <code>if-else</code> chains.</li>\n<li>Creating new data-holding classes and deciding between sealed classes, records, or plain classes.</li>\n<li>Destructuring values from maps, lists, records, or objects.</li>\n<li>Modernizing pre-Dart-3 code to use patterns, exhaustiveness checks, or switch expressions.</li>\n</ul>\n<hr>\n<h2>1. Branches</h2>\n<h3>if / if-case</h3>\n<pre><code>// Standard if\nif (score &gt;= 90) {\n  grade = 'A';\n} else if (score &gt;= 80) {\n  grade = 'B';\n} else {\n  grade = 'C';\n}\n\n// if-case: match and destructure against a single pattern\nif (pair case [int x, int y]) {\n  print('$x, $y');\n}\n</code></pre>\n<ul>\n<li><code>if</code> conditions must evaluate to a <code>bool</code>.</li>\n<li>In <code>if-case</code>, variables declared in the pattern are scoped to the matching branch.</li>\n<li>If the pattern does not match, control flows to the <code>else</code> branch (if present).</li>\n</ul>\n<h3>switch statements</h3>\n<pre><code>switch (command) {\n  case 'quit':\n    quit();\n  case 'start' || 'begin': // logical-or pattern\n    startGame();\n  default:\n    print('Unknown command');\n}\n</code></pre>\n<ul>\n<li>Each matched <code>case</code> body executes and jumps to the end — <code>break</code> is <strong>not required</strong>.</li>\n<li>Non-empty cases can end with <code>continue</code>, <code>throw</code>, or <code>return</code>.</li>\n<li>Use <code>default</code> or <code>_</code> to handle unmatched values.</li>\n<li>Empty cases fall through; use <code>break</code> to prevent fallthrough in an empty case.</li>\n<li>Use <code>continue</code> with a label for non-sequential fallthrough.</li>\n<li>Use logical-or patterns (<code>case a || b</code>) to share a body between cases.</li>\n</ul>\n<h3>switch expressions</h3>\n<pre><code>final color = switch (shape) {\n  Circle() =&gt; 'red',\n  Square() =&gt; 'blue',\n  _ =&gt; 'unknown',\n};\n</code></pre>\n<ul>\n<li>Omit <code>case</code>; use <code>=&gt;</code> for bodies; separate cases with commas.</li>\n<li>Default must use <code>_</code> (not <code>default</code>).</li>\n<li>Produces a value.</li>\n</ul>\n<h3>Exhaustiveness</h3>\n<ul>\n<li>Dart checks exhaustiveness in <code>switch</code> statements and expressions at compile time.</li>\n<li>Use <code>default</code>/<code>_</code>, enums, or <code>sealed</code> types to satisfy exhaustiveness.</li>\n</ul>\n<pre><code>sealed class Shape {}\nclass Circle extends Shape {}\nclass Square extends Shape {}\n\n// Dart knows all subtypes — no default needed:\nString describe(Shape s) =&gt; switch (s) {\n  Circle() =&gt; 'circle',\n  Square() =&gt; 'square',\n};\n</code></pre>\n<h3>Guard clauses</h3>\n<pre><code>switch (point) {\n  case (int x, int y) when x == y:\n    print('Diagonal: $x');\n  case (int x, int y):\n    print('$x, $y');\n}\n</code></pre>\n<ul>\n<li>Add <code>when condition</code> after a pattern to further constrain matching.</li>\n<li>Usable in <code>if-case</code>, <code>switch</code> statements, and <code>switch</code> expressions.</li>\n<li>If the guard is <code>false</code>, execution proceeds to the next case.</li>\n</ul>\n<hr>\n<h2>2. Patterns</h2>\n<p>Patterns represent the <strong>shape</strong> of a value for matching and destructuring.</p>\n<h3>Uses</h3>\n<pre><code>// Variable declaration\nvar (a, [b, c]) = ('str', [1, 2]);\n\n// Variable assignment (swap)\n(b, a) = (a, b);\n\n// for-in loop destructuring\nfor (final MapEntry(:key, :value) in map.entries) { ... }\n\n// switch / if-case (see Branches section)\n</code></pre>\n<ul>\n<li>Wildcard <code>_</code> ignores parts of a matched value.</li>\n<li>Rest elements (<code>...</code>) in list patterns ignore remaining elements.</li>\n<li>Case patterns are <strong>refutable</strong>: if no match, execution continues to the next case.</li>\n<li>Destructured values in a case become local variables scoped to that case body.</li>\n</ul>\n<h3>Object patterns</h3>\n<pre><code>var Foo(:one, :two) = myFoo;\n</code></pre>\n<h3>JSON / nested data validation</h3>\n<pre><code>if (data case {'user': [String name, int age]}) {\n  print('$name, $age');\n}\n</code></pre>\n<hr>\n<h2>3. Pattern Types</h2>\n<table>\n<thead>\n<tr>\n<th>Pattern</th>\n<th>Syntax</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Logical-or</td>\n<td><code>p1 \\|\\| p2</code></td>\n<td>Matches if any branch matches. All branches must bind the same variables.</td>\n</tr>\n<tr>\n<td>Logical-and</td>\n<td><code>p1 &amp;&amp; p2</code></td>\n<td>Matches if both match. Variable names must not overlap.</td>\n</tr>\n<tr>\n<td>Relational</td>\n<td><code>== c</code>, <code>&lt; c</code>, <code>&gt;= c</code></td>\n<td>Compares value to a constant. Combine with <code>&amp;&amp;</code> for ranges.</td>\n</tr>\n<tr>\n<td>Cast</td>\n<td><code>subpattern as Type</code></td>\n<td>Asserts type, then matches inner pattern. Throws if type mismatch.</td>\n</tr>\n<tr>\n<td>Null-check</td>\n<td><code>subpattern?</code></td>\n<td>Matches non-null; binds non-nullable type.</td>\n</tr>\n<tr>\n<td>Null-assert</td>\n<td><code>subpattern!</code></td>\n<td>Matches non-null or throws. Use in declarations to eliminate nulls.</td>\n</tr>\n<tr>\n<td>Constant</td>\n<td><code>42</code>, <code>'str'</code>, <code>const Foo()</code></td>\n<td>Matches if value equals the constant.</td>\n</tr>\n<tr>\n<td>Variable</td>\n<td><code>var name</code>, <code>final Type name</code></td>\n<td>Binds matched value to a new variable. Typed form only matches the declared type.</td>\n</tr>\n<tr>\n<td>Wildcard</td>\n<td><code>_</code>, <code>Type _</code></td>\n<td>Matches any value without binding.</td>\n</tr>\n<tr>\n<td>Parenthesized</td>\n<td><code>(subpattern)</code></td>\n<td>Controls precedence.</td>\n</tr>\n<tr>\n<td>List</td>\n<td><code>[p1, p2]</code></td>\n<td>Matches lists by position. Length must match unless a rest element is used.</td>\n</tr>\n<tr>\n<td>Rest element</td>\n<td><code>...</code>, <code>...rest</code></td>\n<td>Matches arbitrary-length tails or collects remaining elements.</td>\n</tr>\n<tr>\n<td>Map</td>\n<td><code>{'key': subpattern}</code></td>\n<td>Matches maps by key. Missing keys throw <code>StateError</code>.</td>\n</tr>\n<tr>\n<td>Record</td>\n<td><code>(p1, p2)</code>, <code>(x: p1, y: p2)</code></td>\n<td>Matches records by shape; field names can be omitted if inferred.</td>\n</tr>\n<tr>\n<td>Object</td>\n<td><code>ClassName(field: p)</code></td>\n<td>Matches by type and destructures via getters. Extra fields ignored.</td>\n</tr>\n</tbody>\n</table>\n<ul>\n<li>Use parentheses to group lower-precedence patterns.</li>\n<li>All pattern types can be <strong>nested and combined</strong>.</li>\n</ul>\n<hr>\n<h2>4. Records</h2>\n<pre><code>// Create\nvar record = ('first', a: 2, b: true, 'last');\n\n// Type annotation\n({int a, bool b}) namedRecord;\n\n// Access\nprint(record.$1);   // positional: 'first'\nprint(record.a);    // named: 2\n</code></pre>\n<ul>\n<li>Records are <strong>anonymous, immutable, fixed-size</strong> aggregates.</li>\n<li>Each field can have a different type (heterogeneous).</li>\n<li>Fields are accessed via built-in getters (<code>$1</code>, <code>$2</code>, <code>.name</code>); no setters.</li>\n<li>Two records are equal if they have the same shape and equal field values.</li>\n<li><code>hashCode</code> and <code>==</code> are automatically defined.</li>\n</ul>\n<h3>Multiple return values</h3>\n<pre><code>(String name, int age) userInfo(Map&lt;String, dynamic&gt; json) {\n  return (json['name'] as String, json['age'] as int);\n}\n\nvar (name, age) = userInfo(json);\n// Named fields:\nfinal (:name, :age) = userInfo(json);\n</code></pre>\n<h3>Records vs. data classes</h3>\n<p>Use a <strong>record</strong> when:</p>\n<ul>\n<li>Returning multiple values from a single function (small, one-time use).</li>\n<li>Grouping a few values locally with no reuse across the codebase.</li>\n<li>You need structural equality with no additional behavior.</li>\n</ul>\n<p>Use a <strong>class</strong> when:</p>\n<ul>\n<li>The type is reused across multiple files or features.</li>\n<li>You need methods, encapsulation, inheritance, or <code>copyWith</code>.</li>\n<li>The type is part of a public API or long-lived data model.</li>\n<li>Changing the shape must be caught by the type system across the codebase.</li>\n</ul>\n<h3>Other best practices</h3>\n<ul>\n<li>Use <code>typedef</code> for record types to improve readability and maintainability.</li>\n<li>Changing a record type alias does not guarantee type safety across the codebase — only classes provide full abstraction.</li>\n</ul>\n<hr>\n<h2>5. Migration Workflow</h2>\n<p>When modernizing pre-Dart-3 code, follow these steps:</p>\n<h3>Step 1 — Replace if-else chains with switch expressions</h3>\n<pre><code>// Before (pre-Dart 3)\nString label;\nif (status == Status.loading) {\n  label = 'Loading...';\n} else if (status == Status.success) {\n  label = 'Done';\n} else {\n  label = 'Error';\n}\n\n// After (Dart 3)\nfinal label = switch (status) {\n  Status.loading =&gt; 'Loading...',\n  Status.success =&gt; 'Done',\n  Status.error =&gt; 'Error',\n};\n</code></pre>\n<h3>Step 2 — Convert abstract class hierarchies to sealed classes</h3>\n<pre><code>// Before\nabstract class Result {}\nclass Success extends Result { final String data; Success(this.data); }\nclass Failure extends Result { final String error; Failure(this.error); }\n\n// After — enables exhaustive switch\nsealed class Result {}\nfinal class Success extends Result { const Success(this.data); final String data; }\nfinal class Failure extends Result { const Failure(this.error); final String error; }\n</code></pre>\n<h3>Step 3 — Use destructuring for multiple return values</h3>\n<p>Replace wrapper classes used solely for returning multiple values with records.</p>\n<h3>Step 4 — Validate</h3>\n<p>Run <code>dart analyze</code> to confirm exhaustiveness and type safety after each change.</p>\n<hr>\n","files":[{"path":"SKILL.md","sizeBytes":8096,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-08-30T09:58:05.079698Z","sha256":"DD38F06584D94DB9FFB212E6C3FCB022D7D409A7ADE110905BD4A0CB92D2751A","sizeBytes":3539},"review":null,"source":{"repositoryUrl":"https://github.com/evanca/flutter-ai-rules","path":"skills/dart-3-updates","license":"MIT","commit":"7b9cce235714ae17acbae896b1e8c8627e128f4c","subtreeSha":"697E24D0EA5312EA53D059BE6EA012B7EFD1E13E379198656B8B9895DE5E9AAF","lastSyncedAt":"2026-09-29T23:32:26.661053Z"},"reviewedAt":"2026-08-30T10:13:23.554764Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/evanca/flutter-ai-rules/tree/main/skills/dart-3-updates"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install evanca-flutter-ai-rules@llmmart"},{"target":"git","command":"git clone https://github.com/evanca/flutter-ai-rules.git"}]}