BoilerplateHub

AI Coding Agent Setup for Flutter

Flutter's API has been stable for years but its widget catalogue has churned, and Dart's move to sound null safety split training data into pre and post eras that do not compile together. An agent needs the Flutter and Dart versions, the state management approach the project actually uses, and whether Material 3 is enabled. Layout errors here are constraint errors rather than style errors, so an agent that does not reason about bounded and unbounded constraints will keep producing overflow warnings it cannot explain.

Set up your agent

The rules file for Flutter

Save this as AGENTS.md in your project root, and symlink CLAUDE.md to it so Claude Code reads the same rules. Adapt the commands to match your repository before committing it.

AGENTS.md
Download
# AGENTS.md

This is a Flutter application for iOS and Android using Riverpod, go_router, and code generation.

## Project

Stack: Flutter stable channel, Dart 3, Riverpod (code generated providers), go_router, freezed, json_serializable, dio.

Layout:

- `lib/main.dart` entry point and provider scope
- `lib/app/router.dart` go_router configuration, the single source of routes
- `lib/app/theme.dart` ThemeData, colors, typography
- `lib/features/<feature>/presentation/` screens and widgets
- `lib/features/<feature>/application/` providers and controllers
- `lib/features/<feature>/domain/` freezed models and value objects
- `lib/features/<feature>/data/` repositories and API clients
- `lib/shared/` widgets and utilities used by more than one feature
- `test/` mirrors `lib/`

Generated files end in `.freezed.dart`, `.g.dart`. They are outputs, not sources.

## Commands

```bash
flutter pub get                                    # install dependencies
flutter run                                        # run on the connected device
flutter run -d ios / -d android                    # target a platform
dart run build_runner build --delete-conflicting-outputs   # codegen, run after model or provider changes
dart run build_runner watch --delete-conflicting-outputs   # codegen in watch mode
flutter analyze                                    # static analysis, must be clean
dart format lib test
flutter test                                       # unit and widget tests
flutter test test/features/auth                    # one directory
flutter build apk --release
flutter build ipa --release
flutter clean                                      # when the build cache is suspect
```

After changing any freezed model, JSON model, or annotated provider, run build_runner before running or testing. A missing codegen step shows up as unresolved symbols.

## Platform notes

- Permissions: declare in `android/app/src/main/AndroidManifest.xml` and `ios/Runner/Info.plist`. Both are required, and iOS needs a human readable usage string.
- Minimum versions: iOS 13, Android minSdk 23. Do not raise them without approval.
- Android system back is handled by go_router. Use `PopScope` for any screen that must confirm before leaving.
- Use `Theme.of(context).platform` for behavioral differences, not `Platform.isIOS`, so widget tests stay controllable.
- Safe areas: wrap scaffold bodies in `SafeArea`. Do not hardcode notch or status bar heights.
- Adding or upgrading a plugin with native code requires `flutter clean` and a full rebuild, hot reload will not pick it up.
- Test on both a physical Android device and an iOS simulator before calling a UI change done.

## Code style

- Widgets are classes, never functions returning a Widget. Extract a `StatelessWidget` instead of a `_buildX` method.
- Use `const` constructors wherever possible. `flutter analyze` must pass with zero warnings.
- State lives in providers. A `StatefulWidget` is only for animation controllers, focus nodes, and text controllers.
- Watch providers with `ref.watch` in build and `ref.read` in callbacks. Never call `ref.read` during build.
- Models are freezed classes with `fromJson`. No hand written `copyWith` or `==`.
- Repositories return domain models, never raw `Map<String, dynamic>`.
- Navigation goes through named routes in `lib/app/router.dart`. No `Navigator.push` with an inline `MaterialPageRoute`.
- Colors, spacing, and text styles come from the theme. No raw `Color(0xFF...)` in a widget.
- Async work in the UI layer goes through `AsyncValue` and renders loading and error states explicitly.

## Boundaries

Do not touch without explicit instruction:

- Any `.freezed.dart` or `.g.dart` file. Change the source and rerun build_runner.
- `ios/` and `android/` build configuration, signing, Gradle files, and `Podfile.lock`.
- `pubspec.lock`. Change `pubspec.yaml` and run `flutter pub get`.
- App identifiers, version, and build number in `pubspec.yaml`.
- `.github/workflows/`, fastlane configuration, and store metadata.

Needs human review: new plugins with native dependencies, permission additions, deep link configuration, and any change to `lib/app/router.dart` route names.

## Testing

- `flutter test` runs unit and widget tests.
- Required unit tests: repositories, controllers and notifiers, and any pure Dart helper.
- Widget tests cover screens with conditional rendering, using `ProviderScope` overrides for fakes.
- Integration tests in `integration_test/` cover sign in and the primary user flow only, they are slow and stay minimal.
- Golden tests are used only for shared design system widgets. Regenerate deliberately, never with a blanket update.
- A bug fix ships with a test that fails before the fix.

## Git workflow

- Branch from `main`: `feat/short-description`, `fix/short-description`.
- Conventional commits: `feat(profile): add avatar upload`.
- Generated files are committed, but regenerate them in a separate commit from behavior changes so the diff stays readable.
- PR description includes screenshots from both platforms for any UI change.
- Never commit to `main`.
What each section does →

What agents get wrong in Flutter projects

Each of these is worth a line in your rules file, because the model will otherwise repeat it every session.

Falling back to setState in a project with real state management

A codebase using Riverpod, Bloc, or Provider will still get StatefulWidget plus setState from an agent, because that is the simplest working answer and the surrounding files may not show the pattern. The result is state that no other widget can observe and that bypasses your testing setup. Add to your rules file: 'This project uses [your solution]. New feature state goes through it. setState is acceptable only for purely local widget concerns such as an animation flag or a text field focus state.'

Emitting removed widgets and pre-null-safety Dart

RaisedButton, FlatButton, OutlineButton, the new keyword, and nullable-by-default parameter handling all still appear in agent output, and none of them compile against a current SDK. The failure is at least loud, but it costs a full round trip every time. State it: 'Flutter 3.x with sound null safety and Material 3. Use ElevatedButton, TextButton, and OutlinedButton. Never emit the new keyword or code that assumes implicit nullability.'

Using BuildContext after an await

Agents write an async handler that awaits a network call and then calls Navigator.of(context) or shows a SnackBar, without checking that the widget is still mounted. If the user navigated away during the await, this throws or targets a disposed element. Add: 'After any await in a widget or State method, check mounted before touching BuildContext. The use_build_context_synchronously lint must stay enabled and its warnings are errors.'

Nesting scrollables without resolving constraints

Agents put a ListView inside a Column, or a Column inside a Column, and hit an unbounded height constraint that renders as a yellow overflow stripe or a hard exception. They then add shrinkWrap: true as a blanket fix, which works visually and destroys scroll performance on long lists. Write: 'Resolve unbounded constraints with Expanded or Flexible, or use CustomScrollView with slivers. shrinkWrap is only for genuinely short, non-scrolling lists and its use needs a comment.'

Leaving controllers and subscriptions undisposed

AnimationController, TextEditingController, ScrollController, and StreamSubscription all get created in initState by agents and then never disposed, which leaks and produces setState-after-dispose errors that surface far from the cause. Add: 'Every controller or subscription created in initState is disposed in dispose. If a widget has initState it must have a matching dispose unless there is nothing to release.'

Omitting const constructors throughout the tree

Agents build widget trees with no const anywhere, so subtrees that never change are rebuilt on every parent rebuild. Nothing breaks, the app just does more work per frame than it needs to, and it compounds in long lists. Write: 'flutter_lints is enabled with prefer_const_constructors and prefer_const_literals_to_create_immutables. Mark every widget const where the analyzer allows it, and run dart analyze before finishing.'

MCP servers worth adding

MCP gives the agent access to systems outside your codebase. These are the ones that pay off in a Flutter project.

Server What it does Why here
Filesystem MCP server Scoped read and write access to the project directory. Reading pubspec.yaml and the analysis options file is how the agent learns the SDK constraint and lint rules it must satisfy.
Git MCP server History, diffs, and blame. Diffs show which state management pattern the team has been converging on, which the current file layout alone may not reveal.
GitHub MCP server Reads issues, releases, and pull requests. Plugin support for a given platform, especially web and desktop, is usually documented only in the plugin's issue tracker.
Sentry MCP server Pulls crash reports and stack traces. Dart exceptions from disposed widgets and async gaps appear on real devices long after the frame that caused them.
When to use MCP instead of a skill →

Claude skills that fit this stack

Browse the full skills directory →

Start from a codebase the agent understands

Rules files help, but they cannot fix sprawling architecture. A conventional Flutter codebase gets more out of an agent than a clever one does.

Other frameworks