--- 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: `[]`, `{}`, `{}`. - Use `if` and `for` inside collection literals for conditional/iterative building. - Use spread operator: `[...list1, ...list2]`. - Use `whereType()` for type-safe filtering. - Prefer `const` collections when values are known at compile time. ## Async - Use `async`/`await` for all asynchronous operations. - Return `Future` from async functions. Never return `void`. - Use `Stream` for continuous data (events, real-time updates). - Use `Future.wait()` for concurrent independent operations. - Use `Completer` 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` 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` 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` for single values, `Stream` 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` for custom streams. Close in `dispose()`. - Use `Completer` 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=` 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()` for type/value checks. - Use `throwsA(isA())` 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.