dart-3-updates
Skill669 starsChanged 3 months agoMITView on GitHub
Use when writing switch statements, refactoring if-else chains, creating data classes, choosing records vs classes, destructuring values, or modernizing pre-Dart-3 code.
In this repository36 other files it gives its agents.
- AGENTS.mdAGENTS.md
- accessibilitySkill
- architecture-feature-firstSkill
- blocSkill
- code-reviewSkill
- developing-genkit-dartSkill
- effective-dartSkill
- firebase-aiSkill
- firebase-analyticsSkill
- firebase-app-checkSkill
- firebase-authSkill
- firebase-cloud-firestoreSkill
- firebase-cloud-functionsSkill
- firebase-crashlyticsSkill
- firebase-databaseSkill
- firebase-data-connectSkill
- firebase-in-app-messagingSkill
- firebase-messagingSkill
- firebase-remote-configSkill
- firebase-storageSkill
- flutter-app-architectureSkill
- flutter-best-practicesSkill
- flutter-change-notifierSkill
- flutter-errorsSkill
- flutterfire-configureSkill
- flutter-pre-cachingSkill
- flutter-use-column-row-firstSkill
- generate-images-with-firebase-aiSkill
- inclusive-designSkill
- mockitoSkill
- mocktailSkill
- patrol-e2e-testingSkill
- providerSkill
- revenuecat-testingSkill
- riverpodSkill
- testingSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
16 rules. About 0 are common, 6 are distinctive, and 10 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
10
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 13
Use this skill when:
- Very specific: line 44
If the pattern does not match, control flows to the else branch (if present).
- Very specific: line 61
Use default or _ to handle unmatched values.
- Very specific: line 63
Use continue with a label for non-sequential fallthrough.
- Very specific: line 64
Use logical-or patterns (case a || b) to share a body between cases.
- Very specific: line 77
Default must use _ (not default).
- Very specific: line 83
Use default/_, enums, or sealed types to satisfy exhaustiveness.
- Very specific: line 108
Add when condition after a pattern to further constrain matching.
- Very specific: line 110
If the guard is false, execution proceeds to the next case.
- Distinctive: line 174
Use parentheses to group lower-precedence patterns.
- Distinctive: line 175
All pattern types can be nested and combined.
- Distinctive: line 213
Use a record when:
- Distinctive: line 218
Use a class when:
- Very specific: line 226
Use typedef for record types to improve readability and maintainability.
- Distinctive: line 233
When modernizing pre-Dart-3 code, follow these steps:
- Very specific: line 276
Run dart analyze to confirm exhaustiveness and type safety after each change.
≈ “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 3 Updates Skill
Apply Dart 3 language features — branches, patterns, pattern types, and records — correctly and idiomatically.
When to Use
Use this skill when:
- Writing or refactoring
switchstatements orif-elsechains. - Creating new data-holding classes and deciding between sealed classes, records, or plain classes.
- Destructuring values from maps, lists, records, or objects.
- Modernizing pre-Dart-3 code to use patterns, exhaustiveness checks, or switch expressions.
1. Branches
if / if-case
// Standard if
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else {
grade = 'C';
}
// if-case: match and destructure against a single pattern
if (pair case [int x, int y]) {
print('$x, $y');
}ifconditions must evaluate to abool.- In
if-case, variables declared in the pattern are scoped to the matching branch. - If the pattern does not match, control flows to the
elsebranch (if present).
switch statements
switch (command) {
case 'quit':
quit();
case 'start' || 'begin': // logical-or pattern
startGame();
default:
print('Unknown command');
}- Each matched
casebody executes and jumps to the end —breakis not required. - Non-empty cases can end with
continue,throw, orreturn. - Use
defaultor_to handle unmatched values. - Empty cases fall through; use
breakto prevent fallthrough in an empty case. - Use
continuewith a label for non-sequential fallthrough. - Use logical-or patterns (
case a || b) to share a body between cases.
switch expressions
final color = switch (shape) {
Circle() => 'red',
Square() => 'blue',
_ => 'unknown',
};- Omit
case; use=>for bodies; separate cases with commas. - Default must use
_(notdefault). - Produces a value.
Exhaustiveness
- Dart checks exhaustiveness in
switchstatements and expressions at compile time. - Use
default/_, enums, orsealedtypes to satisfy exhaustiveness.
sealed class Shape {}
class Circle extends Shape {}
class Square extends Shape {}
// Dart knows all subtypes — no default needed:
String describe(Shape s) => switch (s) {
Circle() => 'circle',
Square() => 'square',
};Guard clauses
switch (point) {
case (int x, int y) when x == y:
print('Diagonal: $x');
case (int x, int y):
print('$x, $y');
}- Add
when conditionafter a pattern to further constrain matching. - Usable in
if-case,switchstatements, andswitchexpressions. - If the guard is
false, execution proceeds to the next case.
2. Patterns
Patterns represent the shape of a value for matching and destructuring.
Uses
// Variable declaration
var (a, [b, c]) = ('str', [1, 2]);
// Variable assignment (swap)
(b, a) = (a, b);
// for-in loop destructuring
for (final MapEntry(:key, :value) in map.entries) { ... }
// switch / if-case (see Branches section)- Wildcard
_ignores parts of a matched value. - Rest elements (
...) in list patterns ignore remaining elements. - Case patterns are refutable: if no match, execution continues to the next case.
- Destructured values in a case become local variables scoped to that case body.
Object patterns
var Foo(:one, :two) = myFoo;JSON / nested data validation
if (data case {'user': [String name, int age]}) {
print('$name, $age');
}3. Pattern Types
| Pattern | Syntax | Description |
| Logical-or | p1 || p2 | Matches if any branch matches. All branches must bind the same variables. |
| Logical-and | p1 && p2 | Matches if both match. Variable names must not overlap. |
| Relational | == c, < c, >= c | Compares value to a constant. Combine with && for ranges. |
| Cast | subpattern as Type | Asserts type, then matches inner pattern. Throws if type mismatch. |
| Null-check | subpattern? | Matches non-null; binds non-nullable type. |
| Null-assert | subpattern! | Matches non-null or throws. Use in declarations to eliminate nulls. |
| Constant | 42, 'str', const Foo() | Matches if value equals the constant. |
| Variable | var name, final Type name | Binds matched value to a new variable. Typed form only matches the declared type. |
| Wildcard | _, Type _ | Matches any value without binding. |
| Parenthesized | (subpattern) | Controls precedence. |
| List | [p1, p2] | Matches lists by position. Length must match unless a rest element is used. |
| Rest element | ..., ...rest | Matches arbitrary-length tails or collects remaining elements. |
| Map | {'key': subpattern} | Matches maps by key. Missing keys throw StateError. |
| Record | (p1, p2), (x: p1, y: p2) | Matches records by shape; field names can be omitted if inferred. |
| Object | ClassName(field: p) | Matches by type and destructures via getters. Extra fields ignored. |
- Use parentheses to group lower-precedence patterns.
- All pattern types can be nested and combined.
4. Records
// Create
var record = ('first', a: 2, b: true, 'last');
// Type annotation
({int a, bool b}) namedRecord;
// Access
print(record.$1); // positional: 'first'
print(record.a); // named: 2- Records are anonymous, immutable, fixed-size aggregates.
- Each field can have a different type (heterogeneous).
- Fields are accessed via built-in getters (
$1,$2,.name); no setters. - Two records are equal if they have the same shape and equal field values.
hashCodeand==are automatically defined.
Multiple return values
(String name, int age) userInfo(Map<String, dynamic> json) {
return (json['name'] as String, json['age'] as int);
}
var (name, age) = userInfo(json);
// Named fields:
final (:name, :age) = userInfo(json);Records vs. data classes
Use a record when:
- Returning multiple values from a single function (small, one-time use).
- Grouping a few values locally with no reuse across the codebase.
- You need structural equality with no additional behavior.
Use a class when:
- The type is reused across multiple files or features.
- You need methods, encapsulation, inheritance, or
copyWith. - The type is part of a public API or long-lived data model.
- Changing the shape must be caught by the type system across the codebase.
Other best practices
- Use
typedeffor record types to improve readability and maintainability. - Changing a record type alias does not guarantee type safety across the codebase — only classes provide full abstraction.
5. Migration Workflow
When modernizing pre-Dart-3 code, follow these steps:
Step 1 — Replace if-else chains with switch expressions
// Before (pre-Dart 3)
String label;
if (status == Status.loading) {
label = 'Loading...';
} else if (status == Status.success) {
label = 'Done';
} else {
label = 'Error';
}
// After (Dart 3)
final label = switch (status) {
Status.loading => 'Loading...',
Status.success => 'Done',
Status.error => 'Error',
};Step 2 — Convert abstract class hierarchies to sealed classes
// Before
abstract class Result {}
class Success extends Result { final String data; Success(this.data); }
class Failure extends Result { final String error; Failure(this.error); }
// After — enables exhaustive switch
sealed class Result {}
final class Success extends Result { const Success(this.data); final String data; }
final class Failure extends Result { const Failure(this.error); final String error; }Step 3 — Use destructuring for multiple return values
Replace wrapper classes used solely for returning multiple values with records.
Step 4 — Validate
Run dart analyze to confirm exhaustiveness and type safety after each change.
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.

