mcpField
F
flutter-conventions-index

flutter-conventions-index

github.com/zakariaf開発63149 回インストール5 週間前に更新
リポジトリ

flutter-conventions-index について

flutter-conventions-indexは、Flutter/Dartアプリ開発の入口となるスキルです。共通のアーキテクチャとコーディング規則をまとめ、実装、テスト、リリースの作業を専門スキルへ案内します。

主な機能

  1. 01機能単位のレイヤードアーキテクチャ規則
  2. 02Riverpodの状態管理と依存性注入の規約
  3. 03UI、非同期処理、アクセシビリティの指針
  4. 04作業別の専門スキル案内
  5. 05機能実装の手順

活用シーン

  1. 01作業に適したスキルの検索
  2. 02新機能の実装開始
  3. 03アプリ全体のFlutter規約レビュー
  4. 04テストとリリース作業の計画

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/zakariaf/flutter-skills --skill flutter-conventions-index

インストール後、Claude Code で/flutter-conventions-indexを入力して読み込まれたか確認します。

詳細

Flutter の規約 — インデックス

Flutter/Dart アプリの入り口です。すべてのタスクで守る共通の基本ルールを示し、各課題を専門スキルへ案内します。重要なタスクでは、まずここに記載されたルールを適用し、次に専門スキルを開いて詳細を確認してください。ライブラリ内のすべてのスキルに言及しているのは、このスキルだけです。

デフォルトでは単一パッケージの Flutter アプリを想定しています。モノレポ/pub ワークスペースに関するガイダンスは、それを扱うスキル(project-structure-and-packages、codegen-and-toolchain)にのみ記載されています。小規模なアプリでは必要ありません。

絶対に守るべきルール

  1. 機能優先、レイヤー構造、依存は下方向のみ。 機能(フォルダー)ごとにグループ化し、次にレイヤーごとに整理します:View → ViewModel → Repository → Service/data。下位レイヤーが上位レイヤーをインポートすることはなく、依存関係グラフは厳密なDAGです。理由:下方向の非巡回グラフだけが、構造上のスパゲッティコードを防ぐ唯一の手段です。(flutter-architecture, project-structure-and-packages)
  2. ウィジェットはロジックを持ちません。 ウィジェットにビジネスロジック、データアクセス、計算、フォーマット処理を含めてはいけません。状態を読み取って描画するだけです。理由:build()内のロジックはテストできず、予測不能な再ビルドを引き起こします。(widget-composition)
  3. 画面ごとに1つのViewModelを設け、不変状態を扱います。 1つのNotifier/AsyncNotifierが非公開の可変状態を所有し、値の等価性を持つ不変の値として公開します。状態遷移では新しい状態を割り当て、インプレースで変更することはありません。理由:不変の値と単一の所有者により、すべての変更を差分として確認でき、テスト可能になります。(state-management-riverpod)
  4. Riverpod 3.xを状態管理とDIに使います。 最新のNotifier/AsyncNotifier/Future/Streamプロバイダーのみを使用し、プロバイダーをDIコンテナーとします。get_it、package:provider、旧式のStateProvider/StateNotifierProvider/ChangeNotifierProviderは使用しません。理由:構成モデルを1つにすることで、整合させる必要のある第2のDIフレームワークがなくなります。(state-management-riverpod, app-startup-and-bootstrap)
  5. 書き込み経路は1つにします。 ウィジェットもViewModelも、永続化された状態を直接変更しません。すべての変更は、まず永続化し、その後ストリーム経由で再公開するリポジトリメソッドを通します。理由:永続性があり、監視可能な経路を1つにすることで、状態が中途半端に書き込まれることを防げます。(state-management-riverpod, persistence-drift)
  6. 保存せずに導出し、抽象に依存します。 派生値は第2のコピーとしてキャッシュせず、読み取り時に計算します。具象クラスではなく、注入したインターフェースに対してプログラムを記述します。理由:真実の情報源を重複させると同期がずれ、注入による接続点がコードのテストを可能にします。(flutter-architecture, service-boundary-and-native)
  7. 不変モデルと型付きエラーを使います。 ドメイン状態とUI状態は、copyWithと値の等価性を備えたfreezed/sealed値型にします。純粋関数は全域的であり、不確実性を返し、例外をスローしません。回復可能なI/Oでは、sealedなResult<T, F extends Failure>を返し、網羅的に分岐させます。エラーを握りつぶすcatch (_) {}は使用しません。理由:コンパイラーがすべてのケースを強制し、失敗はローカライズされた文字列ではなく、安定したコードを持ちます。(error-handling-typed-results, dart3-idioms-and-coding-standards)
  8. 副作用は注入可能なインターフェースの背後に置きます。 すべてのプラットフォーム/ネイティブの副作用(時計、通知、共有、分析、ストレージ)は、プロバイダー経由のDartインターフェースにし、コンポジションルートで一度だけオーバーライドし、テストではフェイクを使います。「現在時刻」は注入したClockから読み取り、DateTime.now()は使いません。理由:グローバルな副作用は非決定的でテストできない依存関係です。(service-boundary-and-native, value-objects-money-and-units)
  9. 非同期処理を放置しません。 すべてをawaitするか、Futureを明示的に処理します。fire-and-forgetのアローコールバックは使用しません。各awaitの後にBuildContext/mountedを確認し、コントローラー、サブスクリプション、タイマー、シンクを破棄します。理由:破棄されたFutureは、どのLintでも検出できないエラーを握りつぶします。(async-safety)
  10. 小さな単位で実装し、ウィジェットクラスを切り出します。 dart3-idioms-and-coding-standardsが定める複雑度上限の表に従います(メソッド≤30、build()≤80、ファイル≤300、位置引数≤3、ロジックのネスト≤3。ただし例外として、ウィジェットのビルドツリーは≤5までネストできます)。constのStatelessWidgetクラスを切り出し、_buildX()メソッドは使いません。使用可能な箇所ではすべてconstを付けます。理由:小さなconstサブツリーは再ビルドが減り、読みやすくなります。(dart3-idioms-and-coding-standards, widget-composition, flutter-performance)
  11. 名前で役割を示します。 …Screen/…Notifier/…Repository/…Service/…Failureを使用します。Effective Dartの命名規則に厳密に従い、単位を含む完全な語を使い、ファイル名は主要な宣言に合わせます。理由:grepやファイル名を見れば、レイヤーが分かります。(naming-conventions)
  12. RTLとアクセシビリティを設計段階から組み込みます。 方向を持つ(start/end)ジオメトリーのみを使い、左/右をハードコードしません。ユーザー向けの文字列はすべてgen_l10n経由でARBから取得します。セマンティックノードにはすべてラベルとロールを設定し、テキストスケーラーを制限したり、色だけに頼ったりしません。理由:正確さの要件は最初から組み込むのは容易ですが、後付けするのは困難です。(i18n-rtl-l10n, accessibility-as-code)
  13. 固定比率ではなく、コードの構造をテストします。 純粋なコアでは、時計を注入した高速なpackage:testとプロパティ不変条件を使います。それ以外では、flutter_testとmocktail、ProviderContainer.test()とoverrideWithを使い、Notifierではなくリポジトリ/サービスをフェイクにします。アプリ全体を支える受け入れテストを1つ用意します。理由:テストはリスクに応じて行い、不変条件は純粋なコアで担保します。(testing-strategy, widget-golden-and-a11y-testing)
  14. 厳格なLintを最低基準とし、フォーマットは必須です。 バージョンを固定したvery_good_analysisのincludeとstrict-casts/strict-raw-typesを使い、すべてのPRの前にdart formatをクリーンな状態にし、dart analyze --fatal-infosを成功させます。理由:アナライザーの成功は、最も低コストで確認できる正確さの指標です。(lint-and-style-config, dependency-hygiene)

適切なスキルへの案内

次のような場合開くスキル
方向性を確認したい場合、どのスキルを使うべきか分からない場合flutter-conventions-index(このスキル)
レイヤー、機能とパッケージの使い分け、依存関係 DAG を決める場合flutter-architecture
パッケージ、バレル、lib/ と lib/src/、ワークスペースのレイアウトを作成する場合project-structure-and-packages
main()、エラーハンドラー、DI のオーバーライド、ウォームアップ、バックグラウンド移行時や再開時のアプリライフサイクルにおけるフラッシュ処理を実装する場合app-startup-and-bootstrap
Notifier/AsyncNotifier、family/autoDispose、単一の書き込み経路を実装する場合state-management-riverpod
UI の構築やリファクタリング、build()、レイアウト/インセットの実装をする場合widget-composition
アプリのルーター、リダイレクト/認証ガード、ディープリンク、ナビゲーションシェル、トランジション、PopScope、404 を設定する場合navigation-and-routing
Form、同期/非同期のフィールド検証、フォーカストラバーサル、キーボードアクション、入力フォーマッターを実装する場合forms-and-input
ローディング/空/エラー状態、スナックバー、バナー、ダイアログ、取り消し、再試行を表示する場合ui-states-and-feedback
レスポンシブブレークポイント、大画面/タブレット/折りたたみ端末向けのマスターディテール、幅に応じた NavigationRail と BottomNavigationBar の使い分けを実装する場合adaptive-layout
Dart 3 の構文(sealed、レコード、クラス修飾子)を選ぶ場合dart3-idioms-and-coding-standards
クラス/ファイル/変数/ブール値の名前を決める場合naming-conventions
analysis_options.yaml、lint の重大度を設定する場合lint-and-style-config
カクつきを解消し、再ビルドの範囲を絞り、遅延リストや分離 isolate での処理を実装する場合flutter-performance
公開 API に /// ドキュメントコメントを書く場合dartdoc-conventions
非同期コード、context/mounted のガード、破棄処理を実装する場合async-safety
エラー、Result/Failure、データを失わないフローをモデル化する場合error-handling-typed-results
ユニット/ウィジェットテストの作成やフェイクの設定をする場合testing-strategy
ゴールデン、レイアウト、RTL、アクセシビリティのウィジェットテストを作成する場合widget-golden-and-a11y-testing
Drift のテーブル、DAO、.watch ストリームを追加する場合persistence-drift
前方のみのスキーママイグレーションを作成する場合run-migration
ユーザーデータをエクスポート、バックアップ、共有、インポート、復元する場合data-export-and-restore
build_runner、build.yaml、生成コードの方針を設定する場合codegen-and-toolchain
analyze の前にコード生成を実行する場合run-codegen
GitHub Actions の CI パイプラインとゲートを作成する場合ci-pipeline-and-gates
ARB 文字列、ICU 複数形、RTL ジオメトリを追加する場合i18n-rtl-l10n
Semantics、タップ対象、トラバーサル、テキストスケーリングを実装する場合accessibility-as-code
純粋なコアで金額、単位、日付をモデル化する場合value-objects-money-and-units
オンデバイスのリマインダー/通知を実装する場合local-notifications-scheduler
CustomPainter とジェスチャーのヒットテストを実装する場合custom-canvas-and-gestures
アニメーション、触覚フィードバック、お祝い演出、成功/エラーの瞬間を追加する場合motion-and-haptics
すべてのデバイスで同じ結果を再現できるよう、シードまたは日付からコンテンツを生成する場合seeded-determinism-and-golden-vectors
広告、リワード獲得ループ、ペイウォール、権利ゲートを追加する場合ads-and-iap-monetization
ネイティブチャネルまたはプラットフォーム側の副作用を接続する場合service-boundary-and-native
pubspec/ロックファイルを編集する場合、新しい依存関係を監査する場合dependency-hygiene
機能モジュール全体を一貫して作成する場合scaffold-feature-module
トークン → テーマ → コンポーネントの構成を設計する場合(美的な値は対象外)design-system-structure
コミット済みのゴールデン画像のベースラインを再設定する場合run-goldens-rebaseline
リリース前に、アプリごとに一度だけ行うデザイン/QA レビューを実施する場合design-review-workflow
リリースを準備する場合:バージョン管理、署名、シンボル、ストアの申告、段階的公開release-and-store-shipping

機能を構築する際の推奨順序

  1. モデル + 純粋関数 — 不変の値型、全域関数でクロックを注入可能なコア → dart3-idioms-and-coding-standards、value-objects-money-and-units
  2. データレイヤー — リポジトリ = 唯一の信頼できる情報源/単一の書き込み経路。永続化する場合は Drift のテーブル + DAO → persistence-drift、error-handling-typed-results
  3. サービスの境界 — すべての副作用を注入されたインターフェースの背後に置く → service-boundary-and-native
  4. ViewModel — Notifier/AsyncNotifier、family/autoDispose、インテントメソッド → state-management-riverpod
  5. ビュー + ウィジェット — シンプルな ConsumerWidget、小さな const ウィジェットクラス、フォーム、レスポンシブレイアウト、RTL 対応のジオメトリ、そして通常の成功パスを完了とする前に、読み込み/空/エラーの状態を実装します → widget-composition、ui-states-and-feedback、forms-and-input、adaptive-layout、i18n-rtl-l10n、accessibility-as-code
  6. DI + ルーティングを接続 — プロバイダーはコンポジションルートでオーバーライドし、機能のルートを単一の go_router に登録します → app-startup-and-bootstrap、navigation-and-routing、scaffold-feature-module
  7. ドキュメント + テスト — 公開 API に /// を記述し、コアの不変条件を確認するコンテナ/ウィジェットテストではフェイクを使用します → dartdoc-conventions、testing-strategy、widget-golden-and-a11y-testing
  8. プロファイルし、その後 CI ゲートを通過 — プロファイルモードで計測し、フォーマット/解析/コード生成/テストをすべて成功させます → flutter-performance、codegen-and-toolchain、ci-pipeline-and-gates
  9. レビューし、その後リリース — ビルドの最後に設計/QA のレビューを一度実施してから、リリース成果物を準備します。バージョン管理、署名、シンボルのアーカイブ、ストアへの申告、段階的なロールアウトを含みます → design-review-workflow、release-and-store-shipping

全工程を通じて、naming-conventions と lint-and-style-config により、すべての行を適切に保ちます。

ベースラインとなるプロジェクト設定

# analysis_options.yaml
include: package:very_good_analysis/analysis_options.yaml   # pin the version in pubspec
analyzer:
  language:
    strict-casts: true
    strict-raw-types: true
# The PR gate (mirror in CI — see ci-pipeline-and-gates)
dart run build_runner build --delete-conflicting-outputs   # if the app uses codegen
dart format --set-exit-if-changed .
dart analyze --fatal-infos --fatal-warnings
flutter test

すべての機能で繰り返す構成

// Repository = the SINGLE WRITE PATH: persist first, then observers re-emit.
class OrderRepository {
  OrderRepository(this._db, this._clock);
  final AppDatabase _db;
  final Clock _clock;

  /// The only route an order reaches storage. Returns a typed Result, never throws.
  Future<Result<void, PersistFailure>> place(OrderDraft draft) async {
    try {
      await _db.transaction(() async {
        await _db.orderDao.insert(draft.toRow(_clock.now())); // durable FIRST
      });
      return const Ok(null); // watchers over the DAO re-emit on commit
    } on DriftException catch (e, s) {
      return Err(PersistFailure.write(cause: e, stack: s));
    }
  }
}

// ViewModel = one StreamNotifier per screen; state tracks EVERY DAO emission.
// Riverpod 3.x: extend the unified base class, opt into autoDispose on the provider.
class OrderListNotifier extends StreamNotifier<List<Order>> {
  @override
  Stream<List<Order>> build() =>
      ref.watch(orderRepositoryProvider).recent(); // re-emits on every commit

  Future<void> place(OrderDraft draft) async {
    final result = await ref.read(orderRepositoryProvider).place(draft);
    switch (result) {
      case Ok():        break;            // the watched stream re-emits the new state
      case Err(:final failure): state = AsyncError(failure, StackTrace.current);
    }
  }
}

final orderListProvider =
    StreamNotifierProvider.autoDispose<OrderListNotifier, List<Order>>(
  OrderListNotifier.new,
);

// View = dumb ConsumerWidget: reads one notifier, renders, no logic.
class OrderListScreen extends ConsumerWidget {
  const OrderListScreen({super.key});
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ref.watch(orderListProvider).when(
      loading: () => const _OrdersSkeleton(),
      error: (e, _) => _RetryView(onRetry: () => ref.invalidate(orderListProvider)),
      data: (orders) => _OrderListView(orders: orders),
    );
  }
}

アンチパターン

  • ウィジェット内のデータアクセスやビジネスロジック、肥大化した build()、または _buildXxx() ヘルパーメソッド — テストが困難で、再ビルドにも適しません。
  • アプリ/共有状態に setState を使うことや、UI が変更する可変モデル — 単一の所有者 + 不変状態のルールに反します。
  • get_it/package:provider/レガシーな StateProvider/StateNotifierProvider/ChangeNotifierProvider — すり合わせが必要な、2つ目の DI/状態管理フレームワークになります。
  • ウィジェットや ViewModel が DAO に直接書き込むことや、「再公開してから永続化」すること — 単一の書き込み経路を迂回し、書き込み途中の状態を公開する可能性があります。
  • ドメインコードでの DateTime.now() — 非決定的な依存関係です。Clock を注入してください。
  • エラーを握りつぶすこと(catch (_) {})、型付けされていない例外を UI に漏らすこと、または Future を放置すること — どのリンターも検出できないサイレントな失敗につながります。
  • 左右のハードコード、生の数字文字列、またはウィジェット内のユーザー向け文字列リテラル — RTL とローカライズに構造上対応できなくなります。
  • 適切な型付けを避けるための !/late/dynamic、包括的な // ignore、dart format への抵抗 — 実際の欠陥を隠します。
  • プロファイリングせずに最適化すること、またはデバッグモードでプロファイリングすること — 誤った対象を計測します。

完了の定義

  • コードが適切な機能/レイヤーに配置されており、依存関係グラフは下位の方向のみを指しています(ルール 1、6)。
  • View はロジックを持たず、1 つの Notifier/AsyncNotifier が不変の状態を管理しています。レガシーなプロバイダーや追加の DI コンテナーはありません(ルール 2~4)。
  • すべての変更はリポジトリの単一の書き込み経路を通ります。まず永続化し、その後に再公開します(ルール 5)。
  • モデルは値の等価性を持つ不変のものです。純粋関数は全域的であり、復旧可能な I/O は Result とシールドされた Failure を使用し、網羅的に切り替えます。エラーを握りつぶしません(ルール 7)。
  • 副作用は注入されたインターフェースの背後にあります。「現在時刻」は Clock から取得します(ルール 8)。
  • 非同期処理は await されるか、適切に処理されています。await 後は context/mounted を確認し、コントローラーとサブスクリプションは破棄します(ルール 9)。
  • 各単位は制限内です。ウィジェットのクラスを切り出し、const を適用し、名前には役割と単位を含めます(ルール 10~11)。
  • ジオメトリは RTL に対応しています。文字列は ARB に格納し、セマンティクスにはラベルを付け、テキストスケーラーの上限は設定しません(ルール 12)。
  • 公開 API には /// ドキュメントがあり、コアの不変条件を検証するテストと、フェイクを使用したコンテナー/ウィジェットのテストが通ります(ルール 13)。
  • 固定された lint 設定のもとで dart format と dart analyze --fatal-infos が正常に完了します(ルール 14)。
  • タスクの深さに応じて、適切な専門スキルを参照しています。

関連スキル

この表にある各スキルは同階層です。ルーティング表が示すスキルを開いてください。まずコードの配置先については flutter-architecture を、状態の流れについては state-management-riverpod を、Result/Failure の基盤については error-handling-typed-results を参照してください。

参考資料

コメント