diet-guard/docs/DOCS-flutter-rules-state-and-data.md
Krzysztof kuhy Rudnicki 03e14e17f3
Some checks failed
file-length / file length <= 250 lines (push) Has been cancelled
md-naming / markdown naming convention (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Tests / test (3.10) (push) Has been cancelled
Tests / test (3.11) (push) Has been cancelled
Tests / test (3.12) (push) Has been cancelled
release-apk / release (APK) (push) Has been cancelled
Adopt the four markdown namespaces and gate them
Renames this repo's markdown onto README.md / CLAUDE*.md / DOCS*.md /
TODO*.md so a future session can tell a live task from a finished
record without reading the file to find out.

TODO files carry the literal "REMOVE ME AFTER FINISH" and are deleted
when the work lands. Enforced by the md-naming pre-commit hook and CI
workflow, both delegating to the shared gate in kuhyx/utils so this
repo's idea of a valid name cannot drift from every other repo's.

Includes the patch bump this repo's bump-patch-version hook applies to
app/pubspec.yaml on every commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A47aX5DJwmA3QFn3pkEWi7
2026-08-24 15:57:20 +02:00

6.0 KiB

Architecture, lint rules, state and data

Vendored Flutter/Dart AI rules. See CLAUDE.md.

Application Architecture

  • Separation of Concerns: Aim for separation of concerns similar to MVC/MVVM, with defined Model, View, and ViewModel/Controller roles.
  • Logical Layers: Organize the project into logical layers:
    • Presentation (widgets, screens)
    • Domain (business logic classes)
    • Data (model classes, API clients)
    • Core (shared classes, utilities, and extension types)
  • Feature-based Organization: For larger projects, organize code by feature, where each feature has its own presentation, domain, and data subfolders. This improves navigability and scalability.

Lint Rules

Include the package in the analysis_options.yaml file. Use the following analysis_options.yaml file as a starting point:

include: package:flutter_lints/flutter.yaml

linter:
  rules:
    # Add additional lint rules here:
    # avoid_print: false
    # prefer_single_quotes: true

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,
      );
    }