Claude Code for Flutter/Dart: Project Mapping, State Changes, Tests, and Builds
A practical Claude Code workflow for Flutter/Dart: project mapping, pubspec, widgets, state, tests, builds, and devices.
Claude Code helps most in Flutter/Dart work when it acts as a project-aware coding partner, not a one-shot widget generator. A real Flutter change touches more than lib/: it can affect pubspec.yaml, assets, tests, Android and iOS configuration, web constraints, emulator verification, and build commands. In this guide, a widget is a screen building block, state is the data that changes what the user sees, and pubspec is the file that defines dependencies, assets, and SDK constraints.
The working example is a tiny cart screen. It is deliberately small enough to paste into a fresh flutter create project, but it exposes the same mistakes that appear in production apps: creating state inside build, adding packages without a reason, forgetting widget tests, and treating Android, iOS, and web as if they had identical runtime rules.
Keep the official references close while adapting the examples: Flutter CLI, Flutter testing, Widget testing, Flutter pubspec options, Dart pubspec, Platform channels, and Claude Code common workflows. For adjacent ClaudeCodeLab material, pair this with React Native development, testing strategies, and CLAUDE.md best practices.
Map The Project First
Do not start with “build a cart screen.” Start by asking Claude Code to inspect the project without editing. The goal is to create a harness, meaning a safe working frame with the files, commands, constraints, and review criteria the agent must follow.
Read this Flutter project first.
Do not edit files yet.
Report:
1. SDK constraints, dependencies, and assets in pubspec.yaml
2. The structure under lib/, including UI, state, and data layers
3. Existing test style under test/ and integration_test/
4. Enabled targets under android/, ios/, web/, macos/, windows/, and linux/
5. Whether flutter analyze, flutter test, and build commands are documented
Finish with files that are safe to edit and files that should be left alone.
This first pass prevents accidental architecture drift. Claude Code can infer structure, but humans still decide the target platforms, package policy, API contracts, and release gates.
| Area | Let Claude Code handle | Human decision |
|---|---|---|
| Project map | Folder conventions, dependencies, test style | Edit boundaries |
| Widget | UI component, accessibility, responsive behavior | Product copy and design judgment |
| State | Matching setState, ChangeNotifier, Riverpod, Bloc, or existing style | App-wide state strategy |
| pubspec | Dependency and asset proposals | Whether a package is worth adopting |
| Platform | Android/iOS/Web/Desktop differences | Supported targets and store requirements |
| Test/build | Widget tests, integration test outline, commands | Merge and release decision |
Keep pubspec Changes Boring
pubspec.yaml is small, but it controls dependency resolution, assets, SDK constraints, and test packages. Ask Claude Code to justify every dependency before editing. A package added for one screen can change the lockfile and affect unrelated work.
name: cart_ai_demo
description: A small Flutter cart screen used to verify Claude Code prompts.
publish_to: "none"
environment:
sdk: ">=3.4.0 <4.0.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
flutter_lints: ^6.0.0
flutter:
uses-material-design: true
assets:
- assets/images/
For a new sandbox, this proves the baseline. In an existing repository, ask Claude Code to adapt the commands to the current project instead of assuming these exact commands apply.
flutter create cart_ai_demo
cd cart_ai_demo
flutter pub get
flutter analyze
flutter test
The common failures are broken YAML indentation under assets, lockfile churn from unnecessary package upgrades, and adding a state-management package when built-in state is enough. A safe prompt is:
Inspect pubspec.yaml and decide whether the cart change needs a dependency.
Before editing, show the reason, alternatives, affected files, and verification commands.
Do not modify pubspec.yaml or pubspec.lock until I approve the dependency plan.
Change Widget And State Together
The following file can replace lib/main.dart in a fresh flutter create cart_ai_demo project. It uses only Flutter SDK APIs. CartController owns state changes, while the widget renders the current state.
import 'package:flutter/material.dart';
void main() => runApp(const CartDemoApp());
class CartDemoApp extends StatelessWidget {
const CartDemoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Cart AI Demo',
theme: ThemeData(colorSchemeSeed: Colors.teal, useMaterial3: true),
home: const CartSummaryPage(),
);
}
}
class CartLine {
const CartLine({required this.name, required this.price, required this.quantity});
final String name;
final int price;
final int quantity;
CartLine copyWith({int? quantity}) {
return CartLine(name: name, price: price, quantity: quantity ?? this.quantity);
}
}
class CartController extends ChangeNotifier {
final List<CartLine> _lines = const [
CartLine(name: 'Dart notebook', price: 1800, quantity: 1),
CartLine(name: 'Flutter sticker', price: 500, quantity: 2),
].toList();
List<CartLine> get lines => List.unmodifiable(_lines);
int get total => _lines.fold(0, (sum, line) => sum + line.price * line.quantity);
void increment(int index) {
final line = _lines[index];
_lines[index] = line.copyWith(quantity: line.quantity + 1);
notifyListeners();
}
void decrement(int index) {
final line = _lines[index];
if (line.quantity == 1) return;
_lines[index] = line.copyWith(quantity: line.quantity - 1);
notifyListeners();
}
}
class CartSummaryPage extends StatefulWidget {
const CartSummaryPage({super.key});
@override
State<CartSummaryPage> createState() => _CartSummaryPageState();
}
class _CartSummaryPageState extends State<CartSummaryPage> {
late final CartController controller;
@override
void initState() {
super.initState();
controller = CartController();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Cart summary')),
body: AnimatedBuilder(
animation: controller,
builder: (context, _) {
return ListView(
padding: const EdgeInsets.all(16),
children: [
for (final entry in controller.lines.indexed)
ListTile(
title: Text(entry.$2.name),
subtitle: Text('JPY ${entry.$2.price} x ${entry.$2.quantity}'),
trailing: Wrap(
spacing: 8,
children: [
IconButton(
tooltip: 'Decrease ${entry.$2.name}',
onPressed: () => controller.decrement(entry.$1),
icon: const Icon(Icons.remove_circle_outline),
),
IconButton(
tooltip: 'Increase ${entry.$2.name}',
onPressed: () => controller.increment(entry.$1),
icon: const Icon(Icons.add_circle_outline),
),
],
),
),
const Divider(),
Text(
'Total: JPY ${controller.total}',
style: Theme.of(context).textTheme.headlineSmall,
),
],
);
},
),
);
}
}
Ask Claude Code to keep this boundary intact:
Update the cart UI, but keep state changes inside CartController.
Do not create CartController inside build.
Do not add a package.
Add a widget test that proves the total changes after tapping plus and minus.
Add Widget And Device Tests
Widget tests are the fastest regression guard for UI state. With the project named cart_ai_demo, place this in test/cart_summary_test.dart.
import 'package:cart_ai_demo/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('updates the cart total when quantity changes', (tester) async {
await tester.pumpWidget(const CartDemoApp());
expect(find.text('Total: JPY 2800'), findsOneWidget);
await tester.tap(find.byIcon(Icons.add_circle_outline).first);
await tester.pump();
expect(find.text('Total: JPY 4600'), findsOneWidget);
await tester.tap(find.byIcon(Icons.remove_circle_outline).first);
await tester.pump();
expect(find.text('Total: JPY 2800'), findsOneWidget);
});
}
Run the cheap checks first:
flutter analyze
flutter test
For emulator or device verification, use an integration test. It belongs under integration_test/cart_flow_test.dart.
import 'package:cart_ai_demo/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('cart can be changed on a real device', (tester) async {
await tester.pumpWidget(const CartDemoApp());
await tester.tap(find.byIcon(Icons.add_circle_outline).last);
await tester.pumpAndSettle();
expect(find.text('Total: JPY 3300'), findsOneWidget);
});
}
flutter devices
flutter test integration_test -d <device_id>
Use case one is a quantity, filter, or sort control on an existing list. Use case two is a pubspec.yaml cleanup where Claude Code reports unused packages and asset issues before editing. Use case three is a platform-only bug, such as image picker, notifications, location, or external links. Use case four is adding regression tests to a screen that previously had none. In each case, the prompt should include the target files, the expected command, and the failure behavior.
Platform Pitfalls
Flutter gives you one Dart layer, not one deployment environment. Ask Claude Code to inspect native folders when the feature touches permissions, plugins, browser APIs, or app store behavior.
| Target | Common failure | Safer prompt |
|---|---|---|
| Android | Missing manifest permission, minSdk side effect, signing confusion | List the needed permission and explain every manifest or Gradle change |
| iOS | Missing Info.plist usage text, CocoaPods issue, certificate confusion | Separate plist copy, Pod changes, and Xcode checks |
| Web | CORS, unsupported browser API, asset path issue | Identify APIs that do not work on web and propose a fallback |
| Desktop | File permissions, window size, native plugin behavior | Provide OS-specific verification steps |
| Platform channels | Dart/Kotlin/Swift type mismatch | Fix channel name, method names, return type, and error behavior before coding |
Build commands should match the target. Do not ask for iOS release verification on a machine that cannot run Xcode.
flutter analyze
flutter test
flutter build apk --debug
flutter build web --release
Safe Prompt Templates
Split the work into investigation, implementation, test, platform review, and final review.
Investigation only:
Read lib/, test/, pubspec.yaml, and platform folders. Do not edit. Return the project map and risks.
Implementation:
Change only lib/features/cart. Match the existing state-management style. Do not add dependencies.
pubspec:
If a dependency is needed, pause and show reason, alternatives, affected files, and verification commands.
Verification:
Add or update widget tests. Run flutter analyze and flutter test. Report failures with file names.
Critical review:
Review dispose handling, async work, build-side effects, platform config, accessibility, package churn, and missing tests.
The monetization path should match the reader’s stage. Beginners can grab the free cheatsheet. Teams adopting Claude Code in mobile workflows can use Claude Code training and consultation. Engineers designing repeatable AI guardrails should read harness engineering.
Field note: the best Flutter results came from “map first, small diff second, tests and builds last.” The weak prompt was “make a nice Flutter screen.” It produced a plausible UI, but also hidden state creation in build, package churn, and no platform review. This article’s snippets are structured for a fresh flutter create cart_ai_demo project. The current writing environment does not have Flutter installed, so final device verification still needs to be run in a real Flutter SDK environment with flutter analyze, flutter test, and the relevant platform build.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Obsidian to CLAUDE.md Workflow: Stop Re-explaining Context
Turn Obsidian working notes into concise CLAUDE.md operating notes that make Claude Code sessions easier to resume.
Claude Code Revenue CTA Routing: Send Articles to PDF, Gumroad, and Consultation
A Claude Code workflow for routing article readers to the free PDF, Gumroad products, or consultation by intent.
Claude Code Team Handoff Rules: Review Evidence, Permissions, Rollback, and Revenue Paths
A practical Claude Code handoff format for team review, proof, permission rules, rollback, free PDF, Gumroad, and consultation paths.
Related Products
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.