Use Cases (更新: 2026/6/2)

Claude CodeでFlutter/Dart開発を進める実践ガイド: 設計・状態管理・テストまで

Claude CodeでFlutter/Dart開発を進める手順を、設計把握、pubspec、状態管理、テスト、ビルド確認まで解説。

Claude CodeでFlutter/Dart開発を進める実践ガイド: 設計・状態管理・テストまで

Flutter/Dart開発でClaude Codeを使う価値は、ウィジェットを一発生成することではありません。既存プロジェクトの構造を読み、状態管理、pubspec.yaml、プラットフォーム差分、テスト、ビルド確認まで同じ流れで進められることにあります。Flutterでは画面部品をWidget、表示を変える値をstate、依存関係とアセットの設定ファイルをpubspecと呼びます。ここを曖昧にしたまま依頼すると、動く画面に見えても、Androidだけ権限が足りない、iOSだけ署名で詰まる、widget testが壊れる、という形で後から返ってきます。

この記事では、カート画面の小さな例を使って、Claude Codeに安全に任せる範囲を決める方法をまとめます。harnessは「エージェントの足場」です。つまり、Claude Codeが迷わず作業するためのファイル範囲、禁止事項、確認コマンド、レビュー観点のことです。公式情報は、コマンドはFlutter CLI、テストはFlutter testingWidget testing、pubspecはFlutter pubspec optionsDart pubspec、ネイティブ連携はPlatform channels、Claude Codeの運用はClaude Code common workflowsを基準にします。関連して、React Native開発テスト戦略CLAUDE.mdの書き方も合わせて読むと、AIに任せる範囲を切りやすくなります。

まずプロジェクト地図を作る

最初の依頼でいきなり「カート画面を作って」と頼むと、Claude Codeは手元の情報だけで状態管理やフォルダ構成を推測します。Flutterプロジェクトでは、lib/の層分け、test/の書き方、analysis_options.yamlpubspec.yaml、Android/iOS/Webの有効化状況を先に確認させるほうが安定します。

このFlutterプロジェクトを先に読んでください。
編集はまだしないでください。
確認してほしいこと:
1. pubspec.yaml のSDK制約、依存関係、assets設定
2. lib/ 配下の画面、状態管理、data層の分け方
3. test/ と integration_test/ の有無、既存のテストスタイル
4. android/ ios/ web/ macos/ windows/ linux/ のうち実際に使う対象
5. flutter analyze / flutter test / buildコマンドの実行可否
最後に、触ってよいファイル候補と触らないほうがよいファイルを分けて報告してください。

この時点で人間が決めることは、対象プラットフォーム、状態管理ライブラリの採用可否、API契約、リリースまでの合格ラインです。Claude Codeに任せることは、既存規約の抽出、差分案、テスト案、コマンド確認です。

領域Claude Codeに任せること人間が決めること
プロジェクト地図フォルダ構成、依存関係、テスト方針の調査変更してよい範囲
Widget画面部品、アクセシビリティ、レスポンシブ調整UI文言、デザイン判断
statesetStateChangeNotifier、Riverpodなど既存方針への合わせ込みアプリ全体の状態管理方針
pubspec依存追加、assets設定、lockfile確認追加パッケージの採用判断
platformAndroid/iOS/Web差分の洗い出し対応対象とストア要件
test/buildwidget test、integration test、flutter analyze失敗時のリリース判断

pubspecを雑に触らせない

pubspec.yamlは小さく見えますが、Flutterアプリの依存関係、アセット、SDK制約、テスト用パッケージが集まる重要ファイルです。Claude Codeには、追加したい目的と検証コマンドをセットで渡します。パッケージを入れる前に、既存プロジェクトのSDK制約とlockfileを読ませるのがコツです。

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/

新規検証なら、次の流れで土台を作れます。既存リポジトリでは同じコマンドをそのまま実行するのではなく、Claude Codeに現在の構成へ合わせて提案させます。

flutter create cart_ai_demo
cd cart_ai_demo
flutter pub get
flutter analyze
flutter test

失敗例は3つあります。1つ目はassetsのインデントを壊して画像が読み込めなくなること。2つ目はpubspec.lockを見ずにパッケージを上げて、別画面の依存まで変えること。3つ目は不要な状態管理ライブラリを入れることです。小さな画面なら標準のChangeNotifierで十分な場合があります。RiverpodやBlocを使うなら、既存プロジェクトがすでに採用しているときだけにします。

Claude Codeへの依頼は次のように分けます。

pubspec.yamlを確認し、カート画面に必要な依存があるかだけ判断してください。
新しいパッケージを追加する場合は、理由、影響範囲、代替案、実行する確認コマンドを先に提示してください。
勝手にpubspec.yamlやpubspec.lockを編集しないでください。

Widgetとstateを小さく変える

以下はflutter create cart_ai_demo直後のlib/main.dartに貼れる、依存追加なしのサンプルです。Widgetは「画面の部品」、CartControllerは「画面の状態を変える窓口」です。業務アプリではデータ層やデザインシステムに分けますが、Claude Codeに頼む最初の差分はこのくらい小さく保つとレビューしやすくなります。

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

ここでの落とし穴は、buildの中でCartController()を毎回作ること、notifyListeners()を忘れてUIが更新されないこと、価格計算をWidget内に散らすことです。Claude Codeには「状態変更はcontrollerに閉じる」「buildで副作用を起こさない」「テストで合計金額を確認する」と明記します。

テストを同じ依頼に含める

Widget testは、画面を起動して文字、ボタン、タップ後の状態を確認するテストです。flutter create cart_ai_demoの名前で作った場合、次をtest/cart_summary_test.dartに置けます。プロジェクト名が違う場合はimportだけ合わせます。

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

実行コマンドはシンプルです。

flutter analyze
flutter test

エミュレーターや実機での動作も見るなら、integration testを追加します。これは「ユーザー操作に近い流れ」を確認するテストです。

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>

Claude Codeには次のように頼むと、実装と検証がつながります。

CartSummaryPageの変更に対応するwidget testを追加してください。
テストでは初期合計、増加後の合計、減少後の合計を確認してください。
実装後に flutter analyze と flutter test を実行し、失敗した場合は原因をファイル名つきで報告してください。

プラットフォーム別の落とし穴

Flutterは複数プラットフォームを同じDartコードで扱えますが、ストア申請、権限、ファイルパス、Web制約は共通化できません。Claude Codeには、Dartだけでなくandroid/ios/web/などの差分を見せる必要があります。

対象よくある失敗Claude Codeへの安全な頼み方
AndroidAndroidManifest.xmlの権限不足、minSdk変更の副作用、署名設定の混在権限が必要な機能だけを列挙し、manifest差分と理由を先に出させる
iOSInfo.plistの説明文不足、CocoaPods、証明書とProvisioning Profileの混同plistの文言、Pod変更、Xcodeで確認する項目を分けさせる
WebCORS、ブラウザAPI、URL strategy、画像アセットのパスWebで使えないAPIを洗い出し、代替実装を提案させる
Desktopファイルアクセス権限、ウィンドウサイズ、ネイティブプラグイン差OS別の確認手順を表にさせる
Platform channelsDartとKotlin/Swiftの型ずれ、例外処理漏れchannel名、メソッド名、戻り値、失敗時の挙動を固定する

ビルド確認は、対象に応じて明示します。

flutter analyze
flutter test
flutter build apk --debug
flutter build web --release

iOSのrelease buildや署名確認はmacOSとXcode環境が必要です。Windowsだけで「iOSも確認して」と頼むと、Claude Codeができないことまで完了扱いにしがちです。実行できる環境とできない環境をプロンプトに書いておきます。

3つ以上の実用ユースケース

ユースケース1は、既存の一覧画面に小さな状態変更を追加する作業です。例として、数量変更、フィルタ、並び替えがあります。Claude Codeには「既存の状態管理に合わせる」「新しいパッケージを入れない」「widget testを追加する」と依頼します。

ユースケース2は、pubspec.yamlの整理です。未使用パッケージの候補を探し、assets設定を直し、flutter pub getflutter analyzeで確認します。ここではClaude Codeに即編集させず、変更候補と影響範囲を先に出させます。

ユースケース3は、AndroidまたはiOSだけで起きる不具合の調査です。たとえば画像選択、通知、位置情報、外部リンクです。Dartコードだけでなくmanifest、plist、利用プラグイン、実機ログを一緒に渡すと、推測だけの修正が減ります。

ユースケース4は、テストがない画面への回帰テスト追加です。Claude Codeには「成功系だけでなく失敗系も足す」「pumpAndSettleの乱用を避ける」「find条件をUI文言だけに寄せすぎない」と指示します。文言変更で壊れるテストばかりになると、保守コストが上がります。

安全なプロンプトの型

Flutterでは、一度に「画面、API、状態管理、プラットフォーム設定、テスト」を全部頼むと差分が膨らみます。次の順に分けるほうがレビューしやすくなります。

1. 調査だけ:
このFlutterプロジェクトを読み、lib/test/pubspec/platform設定の地図を作ってください。編集は禁止です。

2. 実装範囲:
lib/features/cart 配下だけを変更してください。状態管理は既存方針に合わせ、新規依存は追加しないでください。

3. pubspec:
依存追加が必要な場合は、編集前に理由、代替案、影響範囲、確認コマンドを提示してください。

4. テスト:
widget testを追加し、flutter analyze と flutter test の結果を報告してください。

5. レビュー:
platform差分、build失敗、dispose漏れ、非同期処理、アクセシビリティ、不要なパッケージ追加を批判的に見てください。

Masaの実務メモとして、Flutter画面の修正で一番効いたのは「まず地図、次に小さな差分、最後にテストとビルド確認」に分けることでした。逆に失敗した依頼は「いい感じにFlutterで作って」というものです。見た目は速く出ますが、pubspec.yamlの無断変更、build内の状態生成、Android/iOS設定の見落としが入りやすくなります。

収益化CTAと検証メモ

チームでClaude CodeをFlutter開発に入れるなら、最初から全自動化を狙うより、レビュー可能な作業単位を作るほうが成果につながります。導入ルールを短くまとめたい場合は無料チートシートを、チーム運用やレビュー基準まで整えたい場合はClaude Code研修・導入相談を使ってください。AIに任せる範囲をさらに深く設計したい場合は、ハーネスエンジニアリング入門が参考になります。

この記事のサンプルは、flutter create cart_ai_demoで作る標準構成を前提に、lib/main.darttest/cart_summary_test.dartintegration_test/へ分けて使える形にしました。この執筆環境にはFlutter SDKが入っていなかったため、実機でのflutter testまでは実行していません。公開前の最終確認では、必ず手元のFlutter SDKでflutter analyzeflutter test、対象プラットフォームのflutter buildを通してください。Claude CodeはFlutter開発を速くしますが、公開できる品質にするのは、範囲指定、公式ドキュメント確認、テスト、実機確認の組み合わせです。

#Claude Code #Flutter #Dart #モバイル開発 #クロスプラットフォーム
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。