CLAUDE.md 925 -> 165. 789 of those lines were a verbatim paste of upstream's flutter/flutter rules.md -- 85% of an always-loaded file given over to a document nobody edits. It moves to five topic files under docs/, linked rather than @-imported: inlining it back would pass the cap while keeping it in context on every turn, which is the cost the cap exists to remove. Every `###`/`####` heading in the upstream block survives the split; the two that do not are its own wrapper headings, replaced by the per-file titles (verified by diffing the sorted heading lists). desktop-performance-findings.md 278 -> 73 + 216. It was already narratively two-part; the author signposted "Part 2" explicitly at the seam. Also updates two things that had drifted: the suite is 313 tests at 100% coverage (not 238), and Conventions now documents the cap, where the gate lives, and the two patterns used to split without breaking callers (re-export barrels, and `part` files where a private State must stay in one library). `bash ~/utils/scripts/check_file_length.sh --all` now exits 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.1 KiB
Flutter AI rules — state, routing and data
State management, data flow, routing, JSON serialization and logging.
Verbatim from flutter/flutter docs/rules/rules.md, split across four files only to keep each under the repo's 250-line cap. Re-fetch periodically to stay current; do not hand-edit.
State Management
-
Built-in Solutions: Prefer Flutter's built-in state management solutions. Do not use a third-party package unless explicitly requested.
-
Streams: Use
StreamsandStreamBuilderfor handling a sequence of asynchronous events. -
Futures: Use
FuturesandFutureBuilderfor handling a single asynchronous operation that will complete in the future. -
ValueNotifier: Use
ValueNotifierwithValueListenableBuilderfor simple, local state that involves a single value.// Define a ValueNotifier to hold the state. final ValueNotifier<int> _counter = ValueNotifier<int>(0); // Use ValueListenableBuilder to listen and rebuild. ValueListenableBuilder<int>( valueListenable: _counter, builder: (context, value, child) { return Text('Count: $value'); }, ); -
ChangeNotifier: For state that is more complex or shared across multiple widgets, use
ChangeNotifier. -
ListenableBuilder: Use
ListenableBuilderto listen to changes from aChangeNotifieror otherListenable. -
MVVM: When a more robust solution is needed, structure the app using the Model-View-ViewModel (MVVM) pattern.
-
Dependency Injection: Use simple manual constructor dependency injection to make a class's dependencies explicit in its API, and to manage dependencies between different layers of the application.
-
Provider: If a dependency injection solution beyond manual constructor injection is explicitly requested,
providercan be used to make services, repositories, or complex state objects available to the UI layer without tight coupling (note: this document generally defaults against third-party packages for state management unless explicitly requested).
Data Flow
- Data Structures: Define data structures (classes) to represent the data used in the application.
- Data Abstraction: Abstract data sources (e.g., API calls, database operations) using Repositories/Services to promote testability.
Routing
-
GoRouter: Use the
go_routerpackage for declarative navigation, deep linking, and web support. -
GoRouter Setup: To use
go_router, first add it to yourpubspec.yamlusing thepubtool'saddcommand.// 1. Add the dependency // flutter pub add go_router // 2. Configure the router final GoRouter _router = GoRouter( routes: <RouteBase>[ GoRoute( path: '/', builder: (context, state) => const HomeScreen(), routes: <RouteBase>[ GoRoute( path: 'details/:id', // Route with a path parameter builder: (context, state) { final String id = state.pathParameters['id']!; return DetailScreen(id: id); }, ), ], ), ], ); // 3. Use it in your MaterialApp MaterialApp.router( routerConfig: _router, ); -
Authentication Redirects: Configure
go_router'sredirectproperty to handle authentication flows, ensuring users are redirected to the login screen when unauthorized, and back to their intended destination after successful login. -
Navigator: Use the built-in
Navigatorfor short-lived screens that do not need to be deep-linkable, such as dialogs or temporary views.// Push a new screen onto the stack Navigator.push( context, MaterialPageRoute(builder: (context) => const DetailsScreen()), ); // Pop the current screen to go back Navigator.pop(context);
Data Handling & Serialization
-
JSON Serialization: Use
json_serializableandjson_annotationfor parsing and encoding JSON data. -
Field Renaming: When encoding data, use
fieldRename: FieldRename.snaketo convert Dart's camelCase fields to snake_case JSON keys.// In your model file import 'package:json_annotation/json_annotation.dart'; part 'user.g.dart'; @JsonSerializable(fieldRename: FieldRename.snake) class User { final String firstName; final String lastName; User({required this.firstName, required this.lastName}); factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json); Map<String, dynamic> toJson() => _$UserToJson(this); }
Logging
-
Structured Logging: Use the
logfunction fromdart:developerfor structured logging that integrates with Dart DevTools.import 'dart:developer' as developer; // For simple messages developer.log('User logged in successfully.'); // For structured error logging try { // ... code that might fail } catch (e, s) { developer.log( 'Failed to fetch data', name: 'myapp.network', level: 1000, // SEVERE error: e, stackTrace: s, ); }