Example CLAUDE.md — Flutter/Dart Project¶
This example shows a CLAUDE.md for a Flutter mobile application using Riverpod for state management, GoRouter for navigation, and Dio for HTTP. It covers Flutter-specific conventions for widget structure, state management, and platform-aware development.
The CLAUDE.md File¶
# Project: Acme Mobile App
Flutter 3.24+ / Dart 3.5+ cross-platform mobile app (iOS + Android). Riverpod state management, GoRouter navigation, Dio HTTP client.
## Commands
- `flutter run` — run on connected device/emulator
- `flutter test` — run all tests
- `flutter test test/features/auth/login_test.dart` — run a single test file
- `flutter analyze` — static analysis (must pass with zero issues)
- `dart format .` — format all Dart files
- `flutter pub get` — install dependencies
- `flutter gen-l10n` — regenerate localization files
- `dart run build_runner build --delete-conflicting-outputs` — run code generation (Freezed, Riverpod)
- `flutter build apk --release` — build Android release
- `flutter build ios --release` — build iOS release
Run `dart format . && flutter analyze && flutter test` before committing.
## Architecture
Feature-first project structure:
- `lib/`
- `app.dart` — MaterialApp setup, theme, router
- `features/` — feature modules, each self-contained:
- `auth/` — login, signup, password reset
- `screens/` — full-screen widgets (LoginScreen, SignupScreen)
- `widgets/` — feature-specific widgets
- `providers/` — Riverpod providers for auth state
- `models/` — auth-related data classes
- `repositories/` — auth API calls
- `home/`, `profile/`, `settings/` — same structure
- `core/`
- `router/` — GoRouter configuration and route definitions
- `theme/` — app theme, colors, typography
- `network/` — Dio client setup, interceptors, error handling
- `storage/` — local storage (SharedPreferences, secure storage)
- `models/` — shared data classes
- `widgets/` — shared reusable widgets (AppButton, AppTextField, etc.)
- `utils/` — pure utility functions
- `l10n/` — localization files
- `test/` — mirrors lib/ structure
- `assets/` — images, fonts, JSON files
## Widget Conventions
- Stateless widgets for UI that depends only on input + providers
- Use `ConsumerWidget` or `ConsumerStatefulWidget` for widgets that read providers
- Extract widgets into separate files when they exceed ~80 lines
- Widget file naming: `login_screen.dart`, `order_card.dart`, `app_button.dart`
- One public widget per file — private helpers in the same file are fine
```dart
class LoginScreen extends ConsumerWidget {
const LoginScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authProvider);
return Scaffold(
appBar: AppBar(title: const Text('Login')),
body: switch (authState) {
AsyncLoading() => const Center(child: CircularProgressIndicator()),
AsyncError(:final error) => ErrorView(message: error.toString()),
AsyncData(:final value) => _LoginForm(user: value),
},
);
}
}
State Management (Riverpod)¶
- Use
riverpod_generatorwith@riverpodannotation for all providers - Providers live in
providers/directory within each feature - Use
AsyncValue<T>for all data that comes from network or database - Use
ref.watchin build methods,ref.readin callbacks - Never store widget references in providers
@riverpod
class Auth extends _$Auth {
@override
FutureOr<User?> build() => ref.read(authRepositoryProvider).getCurrentUser();
Future<void> login(String email, String password) async {
state = const AsyncLoading();
state = await AsyncValue.guard(
() => ref.read(authRepositoryProvider).login(email, password),
);
}
}
Data Classes¶
- Use Freezed for all data classes (models, states, events)
- Run
dart run build_runner build --delete-conflicting-outputsafter modifying Freezed classes - JSON serialization with
@JsonSerializablevia Freezed
@freezed
class User with _$User {
const factory User({
required String id,
required String name,
required String email,
String? avatarUrl,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
Navigation (GoRouter)¶
- All routes defined in
core/router/app_router.dart - Use typed route parameters — no magic strings for paths
- Redirect logic for auth guards in the router configuration
- Deep linking support for all public routes
Testing¶
- Widget tests with
WidgetTesterfor UI behavior - Provider tests using
ProviderContainerfor isolated state testing - Use
mocktailfor mocking (not mockito) - Golden tests for critical UI components in
test/goldens/ - Test naming:
test_[feature]_[scenario].dart
Networking (Dio)¶
- Base client configured in
core/network/api_client.dart - Interceptors for auth token injection, error mapping, and logging
- All API calls go through repository classes — never call Dio directly from widgets or providers
- Use typed response models — never work with raw
Map<String, dynamic>outside repositories
Git¶
- Conventional commits: feat:, fix:, chore:, refactor:
- Run code generation before committing if Freezed or Riverpod files changed
- Run
flutter analyze— zero issues required
Do NOT¶
- Do not use
setStatefor data that could be in a provider - Do not use
BuildContextacross async gaps — checkmountedfirst - Do not hardcode strings — use localization (
context.l10n.someKey) - Do not use
MediaQuery.of(context).sizedirectly — useLayoutBuilderor responsive breakpoints - Do not edit generated files (
*.g.dart,*.freezed.dart) - Do not use
print()— use theloggerpackage ```
Key Sections Explained¶
Architecture — The feature-first structure is critical for Flutter projects. Without it, Claude creates a flat structure that becomes unmanageable. Each feature being self-contained means Claude knows exactly where new code should go.
Widget Conventions — The ConsumerWidget pattern and the Dart 3 switch expression for AsyncValue give Claude a clear template for every screen widget.
State Management — Riverpod with code generation is specific enough that Claude needs explicit examples. The ref.watch vs. ref.read rule and the AsyncValue.guard pattern prevent subtle bugs.
Do NOT — The BuildContext across async gaps rule catches a real Flutter crash scenario. The generated file and setState rules prevent the most common Flutter mistakes Claude makes.
See Also¶
- CLAUDE.md Setup Guide — how to structure your own CLAUDE.md
- Minimal Example — a simpler starting point
- React Example — for web frontend projects
- Monorepo Example — for multi-package setups