--- paths: - "**/*.dart" - "**/pubspec.yaml" - "**/analysis_options.yaml" --- # Dart/Flutter Coding Style > This file extends [common/coding-style.md](../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 ```dart // BAD var count = 0; List 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 ```dart // 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: ```dart sealed class AsyncState { const AsyncState(); } final class Loading extends AsyncState { const Loading(); } final class Success extends AsyncState { const Success(this.data); final T data; } final class Failure extends AsyncState { const Failure(this.error); final Object error; } ``` Always use exhaustive `switch` with sealed types — no default/wildcard: ```dart // 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 ```dart // 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 `await`s anything - Use `Future.wait` / `Future.any` for concurrent operations - Check `context.mounted` before using `BuildContext` after any `await` (Flutter 3.7+) ```dart // 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