Files
everything-claude-code/rules/dart/coding-style.md
2026-04-02 17:48:43 -07:00

4.4 KiB

paths
paths
**/*.dart
**/pubspec.yaml
**/analysis_options.yaml

Dart/Flutter Coding Style

This file extends common/coding-style.md with Dart and Flutter-specific content.

Formatting

  • dart format for all .dart files — enforced in CI (dart format --set-exit-if-changed .)
  • Line length: 80 characters (dart format default)
  • Trailing commas on multi-line argument/parameter lists to improve diffs and formatting

Immutability

  • Prefer final for local variables and const for compile-time constants
  • Use const constructors wherever all fields are final
  • Return unmodifiable collections from public APIs (List.unmodifiable, Map.unmodifiable)
  • Use copyWith() for state mutations in immutable state classes
// BAD
var count = 0;
List<String> items = ['a', 'b'];

// GOOD
final count = 0;
const items = ['a', 'b'];

Naming

Follow Dart conventions:

  • camelCase for variables, parameters, and named constructors
  • PascalCase for classes, enums, typedefs, and extensions
  • snake_case for file names and library names
  • SCREAMING_SNAKE_CASE for constants declared with const at top level
  • Prefix private members with _
  • Extension names describe the type they extend: StringExtensions, not MyHelpers

Null Safety

  • Avoid ! (bang operator) — prefer ?., ??, if (x != null), or Dart 3 pattern matching; reserve ! only where a null value is a programming error and crashing is the right behaviour
  • Avoid late unless initialization is guaranteed before first use (prefer nullable or constructor init)
  • Use required for constructor parameters that must always be provided
// BAD — crashes at runtime if user is null
final name = user!.name;

// GOOD — null-aware operators
final name = user?.name ?? 'Unknown';

// GOOD — Dart 3 pattern matching (exhaustive, compiler-checked)
final name = switch (user) {
  User(:final name) => name,
  null => 'Unknown',
};

// GOOD — early-return null guard
String getUserName(User? user) {
  if (user == null) return 'Unknown';
  return user.name; // promoted to non-null after the guard
}

Sealed Types and Pattern Matching (Dart 3+)

Use sealed classes to model closed state hierarchies:

sealed class AsyncState<T> {
  const AsyncState();
}

final class Loading<T> extends AsyncState<T> {
  const Loading();
}

final class Success<T> extends AsyncState<T> {
  const Success(this.data);
  final T data;
}

final class Failure<T> extends AsyncState<T> {
  const Failure(this.error);
  final Object error;
}

Always use exhaustive switch with sealed types — no default/wildcard:

// BAD
if (state is Loading) { ... }

// GOOD
return switch (state) {
  Loading() => const CircularProgressIndicator(),
  Success(:final data) => DataWidget(data),
  Failure(:final error) => ErrorWidget(error.toString()),
};

Error Handling

  • Specify exception types in on clauses — never use bare catch (e)
  • Never catch Error subtypes — they indicate programming bugs
  • Use Result-style types or sealed classes for recoverable errors
  • Avoid using exceptions for control flow
// BAD
try {
  await fetchUser();
} catch (e) {
  log(e.toString());
}

// GOOD
try {
  await fetchUser();
} on NetworkException catch (e) {
  log('Network error: ${e.message}');
} on NotFoundException {
  handleNotFound();
}

Async / Futures

  • Always await Futures or explicitly call unawaited() to signal intentional fire-and-forget
  • Never mark a function async if it never awaits anything
  • Use Future.wait / Future.any for concurrent operations
  • Check context.mounted before using BuildContext after any await (Flutter 3.7+)
// BAD — ignoring Future
fetchData(); // fire-and-forget without marking intent

// GOOD
unawaited(fetchData()); // explicit fire-and-forget
await fetchData();      // or properly awaited

Imports

  • Use package: imports throughout — never relative imports (../) for cross-feature or cross-layer code
  • Order: dart: → external package: → internal package: (same package)
  • No unused imports — dart analyze enforces this with unused_import

Code Generation

  • Generated files (.g.dart, .freezed.dart, .gr.dart) must be committed or gitignored consistently — pick one strategy per project
  • Never manually edit generated files
  • Keep generator annotations (@JsonSerializable, @freezed, @riverpod, etc.) on the canonical source file only