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