todo-app/docs/flutter-rules-state-and-data.md
Krzysztof kuhy Rudnicki 25e2c9bb20 Split CLAUDE.md and the performance doc; the whole repo is under the cap
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>
2026-08-14 16:46:54 +02:00

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 Streams and StreamBuilder for handling a sequence of asynchronous events.

  • Futures: Use Futures and FutureBuilder for handling a single asynchronous operation that will complete in the future.

  • ValueNotifier: Use ValueNotifier with ValueListenableBuilder for 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 ListenableBuilder to listen to changes from a ChangeNotifier or other Listenable.

  • 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, provider can 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_router package for declarative navigation, deep linking, and web support.

  • GoRouter Setup: To use go_router, first add it to your pubspec.yaml using the pub tool's add command.

    // 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's redirect property 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 Navigator for 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_serializable and json_annotation for parsing and encoding JSON data.

  • Field Renaming: When encoding data, use fieldRename: FieldRename.snake to 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 log function from dart:developer for 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,
      );
    }
    

Code Generation