Dart Standards
Priority: P0 — Language Correctness
Null Safety
- Avoid
!. Prefer local promotion, null-check patterns, and private final fields.
- Use
! only for documented invariants or framework/external boundaries where non-null is guaranteed but not expressible to the analyzer.
- Prefer
?., ??, and null-aware patterns over forced unwrapping.
AVOID late if you need to check whether the variable was initialised — use nullable + null-check instead.
DON'T explicitly initialise variables to null; let the type system express optionality.
Immutability
- Use
const > final > var. Use @freezed for data classes.
- Prefer
final for all class members. Use var only for locally-obvious short-lived locals.
AVOID public late final fields without initializers.
Pattern Matching (Dart 3.x)
Use switch expressions with exhaustive patterns and destructuring. Supported pattern types:
| Pattern |
Example |
| Constant |
case 42: |
| Variable |
case var x: |
| Wildcard |
case _: |
| Object |
case Circle(radius: var r): |
| Record |
case (String name, int age): |
| List |
case [first, ...rest]: |
| Map |
case {'key': var v}: |
| Logical-or |
`case 1 |
| Guard |
case var x when x > 0: |
String describe(Shape s) => switch (s) {
Circle(radius: var r) when r > 10 => 'large circle',
Circle(radius: var r) => 'circle r=$r',
Rectangle(width: var w, height: var h) => '${w}x$h rect',
};
Records
- Use records for returning multiple values:
(String, int).
- Use named fields for clarity beyond two elements:
({String name, int age}).
Class Modifiers (Dart 3.x)
Choose the right modifier to express API intent explicitly:
| Modifier |
Extends outside lib |
Implements outside lib |
Use for |
sealed |
no |
no |
Exhaustive domain state (enables exhaustive switch) |
final |
no |
no |
Closed hierarchy — no extension or implementation |
base |
yes |
no |
Allow inheritance, prevent external implementation |
interface |
no |
yes |
Pure contracts — implementation only |
sealed is implicitly abstract; direct subtypes must be in the same library for exhaustive switching.
- Subclasses of a
sealed class are not implicitly abstract — mark each subtype intentionally.
- Use
final instead of sealed when you want to close external subtyping but still add subtypes later without breaking exhaustive switches.
sealed class AuthState {}
final class Authenticated extends AuthState { final User user; Authenticated(this.user); }
final class Unauthenticated extends AuthState {}
Mixins
- Use
mixin for behaviour shared across unrelated class hierarchies.
- Use
mixin class (Dart 3.0) when the type must also be usable as a standalone class.
- Prefer
mixin over abstract class when no constructor is needed.
Enhanced Enums (Dart 2.17+)
Enums can have fields, constructors, and methods. Prefer over utility classes with static constants.
enum Status {
active('Active'),
inactive('Inactive');
const Status(this.label);
final String label;
}
Extensions
- Use
extension to add utility methods to third-party or built-in types.
- Always name extensions (
extension StringX on String) — unnamed extensions are harder to import selectively.
Wildcards (Dart 3.7+)
Use _ for unused variables in declarations and patterns.
Async
- Prefer
async/await over raw Future.then.
- Use
unawaited() for intentional fire-and-forget; never silently discard a future.
DON'T mark a function async if it contains no await — it adds overhead with no benefit.
AVOID using Completer directly; prefer async/await or StreamController.
AVOID FutureOr<T> as a return type.
AVOID returning nullable Future, Stream, or collection types from public APIs.
- Cancel
StreamSubscriptions and close owned StreamControllers or Sinks.
- Avoid
async void except for framework callbacks that require void.
Error Handling
- Use
on ExceptionType catch (e) — never bare catch without on (swallows everything).
DON'T silently discard caught errors.
- Throw
Error subclasses only for programmatic errors (bugs). Use Exception for recoverable runtime conditions.
- Use
rethrow to re-propagate after partial handling; never re-throw the caught object manually.
- Use
assert() for development-time invariants — stripped in production.
Types
- No
dynamic. Use Object, Object?, or generics.
- Annotate return types and parameter types on all public declarations.
DON'T redundantly annotate initialised local variables — let inference work.
- Use
typedef for named type aliases (typedef UserId = String). Prefer inline function type syntax in parameter positions over typedef.
Members & Constructors
- Use initializing formals:
const User({required this.name}).
- Use
; not {} for empty constructor bodies.
- Never use
new.
DON'T use this. except to redirect constructors or avoid shadowing.
DON'T perform complex calculations or async work inside constructors.
- Use a getter for pure computations:
int get invoiceTotal => not int calcTotal().
Equality
- If you override
operator ==, override hashCode.
- Equality must be reflexive, symmetric, transitive, and stable over time.
- Avoid custom equality on mutable classes; prefer immutable value types.
- Use
identical(this, other) as the fast path before structural comparison.
Priority: P1 — Style & Conventions
Naming
- Types and extensions:
UpperCamelCase
- Members, variables, parameters:
lowerCamelCase
- Files, packages, directories:
lowercase_with_underscores
- Import prefixes:
lowercase_with_underscores
- Constants: prefer
lowerCamelCase (not SCREAMING_CAPS) unless matching generated or existing code style.
- Capitalise acronyms longer than two letters as words:
HttpRequest, parseUrl
DON'T use a leading _ on non-private identifiers.
- Name value-object converters for their target context:
get apiFilterType not get filterType.
Scoping
- No top-level mutable state. Encapsulate in a class or inject via DI.
- Library-private identifiers use
_ prefix.
Strings
- Prefer single quotes. Use double quotes only when the string itself contains a single quote.
- Prefer interpolation over concatenation:
'Hello $name' not 'Hello ' + name.
- Adjacent string literals can be concatenated without
+.
- Omit curly braces in interpolation unless required:
'$name' not '${name}'.
Trailing Commas
Always use trailing commas for multi-line argument lists and collection literals.
Expression Bodies
Prefer => for single-expression functions and getters.
Collections
- Use
.isEmpty / .isNotEmpty — never .length == 0.
- Use collection
if, for, and spread ... for composable collections.
- Type empty collections explicitly:
<String>[], <String, User>{}.
- Prefer
.map, .where, .fold, .any over manual loops where clarity wins.
- Use
.firstOrNull, .lastOrNull, .elementAtOrNull(i) for safe indexed access.
DON'T use Iterable.forEach() with a function literal — use for loops or tear-offs.
DON'T use cast() when a nearby operation will do.
Imports
- Group order:
dart: → package: → relative. Sort each section alphabetically.
- Use relative imports for intra-package files; never
package:app/... within the same package.
- Specify exports in a separate section after all imports.
Tear-offs
Prefer list.forEach(print) over list.forEach((e) => print(e)).
Anti-Patterns
! without a documented invariant, local promotion alternative, or framework boundary
- Overriding
== without hashCode
- Custom equality on mutable classes
var for class members
dynamic anywhere
async on a function with no await
async void outside framework callbacks
- Leaked
StreamSubscription, StreamController, or Sink
- Bare
catch without on
- Global mutable state
new keyword
- Package imports within the same package
FutureOr<T> as a return type
- Logic or async work inside constructors
- Zero-argument methods for pure computations — use a getter
References
Load only what the current task requires:
- tooling — setting up or modifying analysis_options, dart format, DCM, build_runner, pubspec, coverage, CI, or pre-commit hooks
- testing — writing or reviewing unit tests, mocks (mocktail), stream tests, or fake_async time-dependent tests
1---2name: dart3description: Dart 3.x language standards and code quality conventions. Use when writing or reviewing any Dart code — null safety, patterns, sealed classes, records, class modifiers, naming, immutability, collections, async, and import organisation.4---56# Dart Standards78## Priority: P0 — Language Correctness910### Null Safety11- Avoid `!`. Prefer local promotion, null-check patterns, and private `final` fields.12- Use `!` only for documented invariants or framework/external boundaries where non-null is guaranteed but not expressible to the analyzer.13- Prefer `?.`, `??`, and null-aware patterns over forced unwrapping.14- `AVOID late` if you need to check whether the variable was initialised — use nullable + null-check instead.15- `DON'T` explicitly initialise variables to `null`; let the type system express optionality.1617### Immutability18- Use `const` > `final` > `var`. Use `@freezed` for data classes.19- Prefer `final` for all class members. Use `var` only for locally-obvious short-lived locals.20- `AVOID` public `late final` fields without initializers.2122### Pattern Matching (Dart 3.x)23Use `switch` expressions with exhaustive patterns and destructuring. Supported pattern types:2425| Pattern | Example |26|---|---|27| Constant | `case 42:` |28| Variable | `case var x:` |29| Wildcard | `case _:` |30| Object | `case Circle(radius: var r):` |31| Record | `case (String name, int age):` |32| List | `case [first, ...rest]:` |33| Map | `case {'key': var v}:` |34| Logical-or | `case 1 || 2:` |35| Guard | `case var x when x > 0:` |3637```dart38String describe(Shape s) => switch (s) {39 Circle(radius: var r) when r > 10 => 'large circle',40 Circle(radius: var r) => 'circle r=$r',41 Rectangle(width: var w, height: var h) => '${w}x$h rect',42};43```4445### Records46- Use records for returning multiple values: `(String, int)`.47- Use named fields for clarity beyond two elements: `({String name, int age})`.4849### Class Modifiers (Dart 3.x)50Choose the right modifier to express API intent explicitly:5152| Modifier | Extends outside lib | Implements outside lib | Use for |53|---|---|---|---|54| `sealed` | no | no | Exhaustive domain state (enables exhaustive switch) |55| `final` | no | no | Closed hierarchy — no extension or implementation |56| `base` | yes | no | Allow inheritance, prevent external implementation |57| `interface` | no | yes | Pure contracts — implementation only |5859- `sealed` is implicitly abstract; direct subtypes must be in the same library for exhaustive switching.60- Subclasses of a `sealed` class are not implicitly abstract — mark each subtype intentionally.61- Use `final` instead of `sealed` when you want to close external subtyping but still add subtypes later without breaking exhaustive switches.6263```dart64sealed class AuthState {}65final class Authenticated extends AuthState { final User user; Authenticated(this.user); }66final class Unauthenticated extends AuthState {}67```6869### Mixins70- Use `mixin` for behaviour shared across unrelated class hierarchies.71- Use `mixin class` (Dart 3.0) when the type must also be usable as a standalone class.72- Prefer `mixin` over `abstract class` when no constructor is needed.7374### Enhanced Enums (Dart 2.17+)75Enums can have fields, constructors, and methods. Prefer over utility classes with static constants.7677```dart78enum Status {79 active('Active'),80 inactive('Inactive');8182 const Status(this.label);83 final String label;84}85```8687### Extensions88- Use `extension` to add utility methods to third-party or built-in types.89- Always name extensions (`extension StringX on String`) — unnamed extensions are harder to import selectively.9091### Wildcards (Dart 3.7+)92Use `_` for unused variables in declarations and patterns.9394### Async95- Prefer `async/await` over raw `Future.then`.96- Use `unawaited()` for intentional fire-and-forget; never silently discard a future.97- `DON'T` mark a function `async` if it contains no `await` — it adds overhead with no benefit.98- `AVOID` using `Completer` directly; prefer `async/await` or `StreamController`.99- `AVOID` `FutureOr<T>` as a return type.100- `AVOID` returning nullable `Future`, `Stream`, or collection types from public APIs.101- Cancel `StreamSubscription`s and close owned `StreamController`s or `Sink`s.102- Avoid `async void` except for framework callbacks that require `void`.103104### Error Handling105- Use `on ExceptionType catch (e)` — never bare `catch` without `on` (swallows everything).106- `DON'T` silently discard caught errors.107- Throw `Error` subclasses only for programmatic errors (bugs). Use `Exception` for recoverable runtime conditions.108- Use `rethrow` to re-propagate after partial handling; never re-throw the caught object manually.109- Use `assert()` for development-time invariants — stripped in production.110111### Types112- No `dynamic`. Use `Object`, `Object?`, or generics.113- Annotate return types and parameter types on all public declarations.114- `DON'T` redundantly annotate initialised local variables — let inference work.115- Use `typedef` for named type aliases (`typedef UserId = String`). Prefer inline function type syntax in parameter positions over typedef.116117### Members & Constructors118- Use initializing formals: `const User({required this.name})`.119- Use `;` not `{}` for empty constructor bodies.120- Never use `new`.121- `DON'T` use `this.` except to redirect constructors or avoid shadowing.122- `DON'T` perform complex calculations or async work inside constructors.123- Use a getter for pure computations: `int get invoiceTotal =>` not `int calcTotal()`.124125### Equality126- If you override `operator ==`, override `hashCode`.127- Equality must be reflexive, symmetric, transitive, and stable over time.128- Avoid custom equality on mutable classes; prefer immutable value types.129- Use `identical(this, other)` as the fast path before structural comparison.130131---132133## Priority: P1 — Style & Conventions134135### Naming136- Types and extensions: `UpperCamelCase`137- Members, variables, parameters: `lowerCamelCase`138- Files, packages, directories: `lowercase_with_underscores`139- Import prefixes: `lowercase_with_underscores`140- Constants: prefer `lowerCamelCase` (not `SCREAMING_CAPS`) unless matching generated or existing code style.141- Capitalise acronyms longer than two letters as words: `HttpRequest`, `parseUrl`142- `DON'T` use a leading `_` on non-private identifiers.143- Name value-object converters for their target context: `get apiFilterType` not `get filterType`.144145### Scoping146- No top-level mutable state. Encapsulate in a class or inject via DI.147- Library-private identifiers use `_` prefix.148149### Strings150- Prefer single quotes. Use double quotes only when the string itself contains a single quote.151- Prefer interpolation over concatenation: `'Hello $name'` not `'Hello ' + name`.152- Adjacent string literals can be concatenated without `+`.153- Omit curly braces in interpolation unless required: `'$name'` not `'${name}'`.154155### Trailing Commas156Always use trailing commas for multi-line argument lists and collection literals.157158### Expression Bodies159Prefer `=>` for single-expression functions and getters.160161### Collections162- Use `.isEmpty` / `.isNotEmpty` — never `.length == 0`.163- Use collection `if`, `for`, and spread `...` for composable collections.164- Type empty collections explicitly: `<String>[]`, `<String, User>{}`.165- Prefer `.map`, `.where`, `.fold`, `.any` over manual loops where clarity wins.166- Use `.firstOrNull`, `.lastOrNull`, `.elementAtOrNull(i)` for safe indexed access.167- `DON'T` use `Iterable.forEach()` with a function literal — use `for` loops or tear-offs.168- `DON'T` use `cast()` when a nearby operation will do.169170### Imports171- Group order: `dart:` → `package:` → relative. Sort each section alphabetically.172- Use relative imports for intra-package files; never `package:app/...` within the same package.173- Specify exports in a separate section after all imports.174175### Tear-offs176Prefer `list.forEach(print)` over `list.forEach((e) => print(e))`.177178---179180## Anti-Patterns181182- `!` without a documented invariant, local promotion alternative, or framework boundary183- Overriding `==` without `hashCode`184- Custom equality on mutable classes185- `var` for class members186- `dynamic` anywhere187- `async` on a function with no `await`188- `async void` outside framework callbacks189- Leaked `StreamSubscription`, `StreamController`, or `Sink`190- Bare `catch` without `on`191- Global mutable state192- `new` keyword193- Package imports within the same package194- `FutureOr<T>` as a return type195- Logic or async work inside constructors196- Zero-argument methods for pure computations — use a getter197198---199200## References201202Load only what the current task requires:203204- [tooling](refs/tooling.md) — setting up or modifying analysis_options, dart format, DCM, build_runner, pubspec, coverage, CI, or pre-commit hooks205- [testing](refs/testing.md) — writing or reviewing unit tests, mocks (mocktail), stream tests, or fake_async time-dependent tests