---
name: dart-rules
description: "Dart/Flutter coding rules: style, patterns, security, testing. Triggers: .dart, pubspec.yaml, Flutter, Riverpod, Bloc, widget, StatelessWidget, StatefulWidget."
effort: medium
user-invocable: false
allowed-tools: Read
---

# Dart/Flutter Rules

These rules come from `app/rules/dart/` in ai-toolkit. They cover
the project's standards for coding style, frameworks, patterns,
security, and testing in Dart/Flutter. Apply them when writing or
reviewing Dart/Flutter code.

# Dart Coding Style

## Naming
- PascalCase: classes, enums, typedefs, extensions, mixins.
- camelCase: variables, functions, methods, parameters, named constants.
- snake_case: libraries, packages, directories, source files.
- UPPER_SNAKE: not used in Dart. Use camelCase for constants.
- Prefix private members with `_`: `_internalState`, `_helper()`.

## Null Safety
- Enable sound null safety (default since Dart 2.12).
- Use `?` types only when null is semantically meaningful.
- Use `!` operator sparingly. Prefer null checks or `??` fallback.
- Use `late` keyword only when initialization is guaranteed before access.
- Use `required` keyword for mandatory named parameters.

## Classes
- Use `const` constructors for immutable classes.
- Use factory constructors for caching, subtype selection, or validation.
- Use named constructors for clarity: `Point.fromJson(json)`.
- Use `final` fields for immutable properties.
- Use `@immutable` annotation on classes that should be immutable.

## Functions
- Use named parameters for functions with >2 parameters.
- Use `required` for mandatory named parameters.
- Use default values for optional parameters.
- Use fat arrow (`=>`) for single-expression functions.
- Always specify return types for public functions.

## Collections
- Use collection literals: `[]`, `{}`, `<String, int>{}`.
- Use `if` and `for` inside collection literals for conditional/iterative building.
- Use spread operator: `[...list1, ...list2]`.
- Use `whereType<T>()` for type-safe filtering.
- Prefer `const` collections when values are known at compile time.

## Async
- Use `async`/`await` for all asynchronous operations.
- Return `Future<T>` from async functions. Never return `void`.
- Use `Stream<T>` for continuous data (events, real-time updates).
- Use `Future.wait()` for concurrent independent operations.
- Use `Completer<T>` only when wrapping callback-based APIs.

## Imports
- Order: `dart:` SDK, `package:` external, relative project imports.
- Use `show`/`hide` to limit import scope when names conflict.
- Use `as` prefix for namespace conflicts: `import 'package:foo/foo.dart' as foo`.
- Prefer relative imports within the same package.

## Formatting
- Use `dart format` (line length 80) for consistent formatting.
- Use `dart analyze` for static analysis with default lint rules.
- Use `analysis_options.yaml` with recommended lints: `flutter_lints` or `lints`.
- Use trailing commas in multi-line argument lists for cleaner diffs.

# Dart Frameworks

## Flutter
- Use `StatelessWidget` by default. Use `StatefulWidget` only for local state.
- Use `const` constructors and `const` widgets for build optimization.
- Use `Key` parameters for widgets in lists for correct diffing.
- Extract large `build()` methods into smaller widget classes (not methods).
- Use `Theme.of(context)` and `TextTheme` for consistent styling.

## Navigation
- Use `GoRouter` for declarative, type-safe routing.
- Define routes as constants: `static const String home = '/home'`.
- Use `ShellRoute` for persistent navigation bars across routes.
- Use `context.go()` for navigation, `context.push()` for stacking.
- Pass arguments via path parameters or `extra` for complex objects.

## Networking
- Use `dio` for HTTP with interceptors, retry, and cancellation.
- Use `retrofit` (code gen) for type-safe REST client definitions.
- Use interceptors for auth token injection and refresh logic.
- Set timeouts on every request: `connectTimeout`, `receiveTimeout`.
- Use `CancelToken` for cancelling in-flight requests on navigation.

## JSON Serialization
- Use `json_serializable` (+ `build_runner`) for generated `fromJson`/`toJson`. Default `fieldRename: FieldRename.none` uses Dart property names as-is — combined with Effective Dart `lowerCamelCase`, this produces `camelCase` JSON keys with zero configuration.
- Flutter docs recommend: *"best if both server and client follow the same naming strategy"* ([Flutter — JSON and serialization](https://docs.flutter.dev/data-and-backend/serialization/json)). When they do, no mapping is needed.
- When server uses a different convention, prefer `@JsonSerializable(fieldRename: FieldRename.snake)` at the class level (or globally in `build.yaml`) over sprinkling `@JsonKey(name:)` on every field. Community recommendation from the `json_serializable` docs and pub.dev guides.
- Use individual `@JsonKey(name: '...')` only for exceptional cases: external API with mixed conventions, reserved Dart keyword collision (`class`, `is`, `new`), or legacy field rename during deprecation window. Document the reason in a comment.
- For enum / status / permission values on the wire: `UPPER_SNAKE_CASE` is the cross-language community consensus (see `common/coding-style.md` — JSON Wire Format Conventions). Dart enum case names themselves stay `lowerCamelCase` per Effective Dart; map them to uppercase strings in `fromJson`/`toJson` (`value.toUpperCase()` + `switch`).
- Write unit tests asserting both directions (`fromJson` + `toJson`) with explicit expected keys. Catches contract drift at CI time.

## Local Storage
- Use `shared_preferences` for simple key-value persistence.
- Use `drift` (formerly Moor) for type-safe SQLite with reactive queries.
- Use `hive` for fast, lightweight NoSQL local storage.
- Use `flutter_secure_storage` for sensitive data (tokens, passwords).
- Never store secrets in `shared_preferences` (not encrypted).

## Dependency Injection
- Use `get_it` for service locator pattern. Register at app startup.
- Use `injectable` (code gen) for automatic registration from annotations.
- Use Riverpod providers as DI containers for testable architecture.
- Register singletons for services, factories for per-use instances.

## Platform Channels
- Use `MethodChannel` for invoking native (iOS/Android) code.
- Use `EventChannel` for streaming data from native to Dart.
- Use `Pigeon` (code gen) for type-safe platform channel definitions.
- Handle `MissingPluginException` gracefully on unsupported platforms.

## Testing Frameworks
- Use `flutter_test` for widget tests with `WidgetTester`.
- Use `integration_test` package for full app integration tests.
- Use `patrol` for native-aware integration testing (permissions, notifications).
- Use `golden_toolkit` for advanced visual regression testing.

## Build and CI
- Use `flutter build` with `--release` and `--dart-define` for env configuration.
- Use flavors (`--flavor`) for dev/staging/prod build variants.
- Use `flutter analyze` in CI for static analysis enforcement.
- Use `flutter test --coverage` with `lcov` for coverage reporting.

# Dart Patterns

## Error Handling
- Use typed exceptions for domain errors: `class UserNotFoundException implements Exception`.
- Use `try-catch` with specific exception types. Avoid bare `catch (e)`.
- Use `rethrow` to preserve stack trace when re-raising exceptions.
- Use `Result<T, E>` pattern (e.g., `dartz` Either) for expected failures.
- Use `Future.catchError()` only when `async/await` is not applicable.

## State Management (Flutter)
- Use Riverpod for compile-safe, testable state management.
- Use BLoC pattern for event-driven state with clear input/output.
- Use `ChangeNotifier` / `ValueNotifier` for simple local state.
- Use `StateNotifier` (Riverpod) for immutable state transitions.
- Keep state classes immutable. Use `copyWith()` for updates.

## Riverpod
- Use `@riverpod` annotation (code gen) for provider definitions.
- Use `ref.watch()` for reactive dependencies. Use `ref.read()` for one-time access.
- Use `AsyncNotifier` for async state management.
- Use `autoDispose` for providers that should clean up when unused.
- Use `family` modifier for parameterized providers.

## BLoC Pattern
- Separate events (input), states (output), and logic (bloc).
- Use `sealed class` for events and states (exhaustive `switch`).
- Use `Emitter<State>` for emitting state transitions.
- Use `transformEvents()` for debouncing search inputs.
- Use `BlocObserver` for global logging and error tracking.

## Repository Pattern
- Abstract data sources behind repository interfaces.
- Repositories return domain models, not DTOs or raw data.
- Use `Future<T>` for single values, `Stream<T>` for real-time updates.
- Cache data in repository layer when appropriate.
- Inject repositories via constructor. Use Riverpod/GetIt for DI.

## Freezed (Code Generation)
- Use `@freezed` for immutable data classes with `copyWith`, equality, `toString`.
- Use `@freezed` sealed unions for state modeling: `factory State.loading()`.
- Use `when()` / `map()` for exhaustive pattern matching on freezed unions.
- Run `dart run build_runner build` after modifying freezed classes.

## Async Patterns
- Use `Stream.asyncMap()` for transforming streams with async operations.
- Use `StreamController<T>` for custom streams. Close in `dispose()`.
- Use `Completer<T>` to bridge callback APIs to Future-based APIs.
- Use `Timer.periodic()` for polling. Cancel in `dispose()`.
- Use `compute()` (Flutter) for CPU-intensive work on isolates.

## Anti-Patterns
- Using `dynamic` type: defeats type safety. Use `Object?` or generics.
- Not disposing controllers/subscriptions: causes memory leaks.
- Putting business logic in widgets: extract to services/blocs.
- Using `setState()` for global state: use proper state management.
- Deep widget nesting: extract sub-widgets as separate classes.

# Dart Security

## Input Validation
- Validate all user input in form fields with `TextFormField` validators.
- Use `RegExp` for pattern validation (email, phone, URL).
- Sanitize HTML content before rendering. Never use `Html` widget with raw user input.
- Validate deep link parameters before navigation or data loading.
- Limit text input length with `maxLength` on `TextFormField`.

## Network Security
- Use HTTPS exclusively. Configure `SecurityContext` for certificate pinning.
- Use `dio` interceptors for consistent auth header injection.
- Validate SSL certificates in production. Do not disable certificate checks.
- Set connection and read timeouts on all HTTP requests.
- Use `CancelToken` to abort requests when the user navigates away.

## Data Storage
- Use `flutter_secure_storage` for tokens, passwords, and API keys.
- Never store sensitive data in `shared_preferences` (stored in plaintext).
- Encrypt local databases (`drift` with `sqlcipher`, or `hive` with encryption).
- Clear secure storage on user logout.
- Use `kIsWeb` checks to handle web platform storage limitations.

## Authentication
- Use OAuth 2.0 / OIDC with PKCE flow for mobile authentication.
- Store refresh tokens in secure storage. Store access tokens in memory.
- Use `flutter_appauth` for standards-compliant OAuth flows.
- Implement biometric authentication with `local_auth` package.
- Never store credentials in Dart source code or asset files.

## Platform Channel Security
- Validate all data received from native code via platform channels.
- Do not pass sensitive data through `MethodChannel` logging-enabled calls.
- Use `Pigeon` for type-safe channel communication (prevents mismatched types).
- Handle `PlatformException` gracefully for missing native implementations.

## Obfuscation and Hardening
- Use `--obfuscate --split-debug-info=<dir>` for release builds.
- Use `--dart-define` for environment-specific configuration (not secrets).
- Do not embed API keys in the Dart source. Use server-side proxying.
- Use ProGuard rules (Android) and symbol stripping (iOS) for native code.

## WebView Security
- Use `webview_flutter` with JavaScript disabled unless explicitly needed.
- Restrict navigation to allowlisted domains with `NavigationDelegate`.
- Sanitize any data passed from WebView to Dart via JavaScript channels.
- Do not load untrusted URLs in WebViews.

## Dependency Security
- Run `dart pub outdated` regularly. Update dependencies promptly.
- Audit `pubspec.lock` for unexpected transitive dependencies.
- Use `dart pub audit` (when available) for vulnerability scanning.
- Prefer well-maintained packages with high pub.dev scores.
- Pin exact versions in `pubspec.yaml` for production apps.

# Dart Testing

## Framework
- Use `package:test` for pure Dart unit tests.
- Use `package:flutter_test` for Flutter widget and integration tests.
- Use `package:mockito` with `@GenerateMocks` for mock generation.
- Use `package:mocktail` as a simpler alternative (no code generation).

## File Naming
- Test files: `foo_test.dart` in `test/` mirroring `lib/` structure.
- Widget tests: `test/widgets/` for Flutter widget tests.
- Integration tests: `integration_test/` directory (Flutter convention).
- Golden tests: `test/goldens/` for visual regression snapshots.

## Structure
- Use `group()` for organizing related tests.
- Use `setUp()` / `tearDown()` for per-test setup and cleanup.
- Use `setUpAll()` / `tearDownAll()` for expensive one-time setup.
- Name tests descriptively: `test('returns null when user is not found', ...)`.

## Assertions
- Use `expect(actual, matcher)` with built-in matchers.
- Use `equals()`, `isNull`, `isNotNull`, `isA<T>()` for type/value checks.
- Use `throwsA(isA<FormatException>())` for exception testing.
- Use `completion(expected)` for Future assertions.
- Use `emitsInOrder([...])` for Stream emission testing.

## Mocking (Mockito)
- Annotate: `@GenerateMocks([UserRepository])`. Run `build_runner`.
- Stub: `when(mock.getUser(any)).thenAnswer((_) async => user)`.
- Verify: `verify(mock.saveUser(captureAny)).called(1)`.
- Use `verifyNever()` to assert a method was not called.
- Use `throwOnMissingStub()` to catch unstubbed method calls.

## Widget Testing (Flutter)
- Use `testWidgets('description', (tester) async { ... })`.
- Use `tester.pumpWidget(MaterialApp(home: MyWidget()))` to render.
- Use `tester.pump()` to trigger rebuilds after state changes.
- Use `tester.pumpAndSettle()` to wait for animations to complete.
- Use `find.byType()`, `find.text()`, `find.byKey()` for widget lookups.
- Use `tester.tap()`, `tester.enterText()` for interaction simulation.

## Golden Tests
- Use `matchesGoldenFile('goldens/my_widget.png')` for visual comparison.
- Run `flutter test --update-goldens` to regenerate baseline images.
- Use golden tests for complex UI components, not simple widgets.
- Keep golden tests platform-specific (render output varies by OS).

## Best Practices
- Test public API behavior, not implementation details.
- Use `fake` classes (implementing interfaces) for simple test doubles.
- Use `addTearDown()` to register cleanup in the test body.
- Run `flutter test --coverage` and check `coverage/lcov.info`.
- Use `blocTest()` from `bloc_test` package for BLoC testing.
