# Dart

> 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.

- Skill: `ndisisnd/dart` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add ndisisnd/dart`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ndisisnd/dart/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: ndisisnd (https://skillmd.com/u/ndisisnd)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ndisisnd/dart

---


# 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 || 2:` |
| Guard | `case var x when x > 0:` |

```dart
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.

```dart
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.

```dart
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 `StreamSubscription`s and close owned `StreamController`s or `Sink`s.
- 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](refs/tooling.md) — setting up or modifying analysis_options, dart format, DCM, build_runner, pubspec, coverage, CI, or pre-commit hooks
- [testing](refs/testing.md) — writing or reviewing unit tests, mocks (mocktail), stream tests, or fake_async time-dependent tests

