flutter-architecture について
Flutter と Dart の開発者向けに、クリーンアーキテクチャの指針と機能実装の手順を示すガイドです。レイヤーごとの責務やデータフロー、エラー処理、新機能のチェックリストを定めています。
主な機能
- 01機能単位でクリーンアーキテクチャを構成
- 02API・ドメイン・UI のデータモデルを分離
- 03DataSource と Repository でデータアクセスを整理
- 04BLoC と依存性注入の手順を定義
- 05Dart 3 の構文とレイヤー別エラー処理を適用
- 06プラットフォームチャネルとネイティブ連携を規定
活用シーン
- 01Flutter 機能の構成とアーキテクチャの計画
- 02API データから UI 状態への変換
- 03新機能の実装手順の整理
- 04ネイティブチャネルとエラー処理の標準化
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ dhruvanbhalara/ skills --skill flutter-architectureインストール後、Claude Code で/flutter-architectureを入力して読み込まれたか確認します。
詳細
技術スタック
- クロスプラットフォーム開発には Flutter
- 主要なプログラミング言語として Dart
- 状態管理には bloc
- 依存性注入には injectable
- イミュータブルなデータモデルには Dart Mappable
- HTTP ネットワーキングには Dio
- ローカルデータベースには isar
- バックエンドサービスには Firebase
クリーンアーキテクチャ
- ドメインの純粋性:
domainレイヤーは純粋な Dart でなければなりません。package:flutterのインポートは禁止です。 - レイヤー依存関係:
Presentation -> Domain <- Data。Data レイヤーは Domain のインターフェースを実装します。 - Feature-First 2.0:
DataSources(外部/生データ)とRepositories(ドメイン抽象化)を厳密に分離します。
ディレクトリ構造
- 機能ベースのクリーンアーキテクチャパターン(
featureA/bloc、featureA/models、featureA/views)に従ってコードを整理します - 機能をまたぐコンポーネントは
coreディレクトリに配置します - 共有ウィジェットは
core/views/widgetsに種類ごとにまとめます
3 層データモデルパターン
-
API レイヤー(ItemApiModel)
- サーバーから受信した生のデータ構造を表します
- API が提供するすべてのフィールドを含みます
- API のコントラクトと正確に一致する必要があります
-
ドメインレイヤー(Item)
- 内部データモデルを表します
- ビジネスロジックに必要なフィールドのみを含みます
- 不要な API フィールドを取り除きます
-
UI レイヤー(ItemUiState)
- UI レンダリングに最適化されたデータモデルを表します
- 表示するために解析・整形されたデータを含みます
- UI 固有の変換をすべて処理します
データモデルのルール
- イミュータブルな UI 状態を定義するには Dart Mappable を使用します
- 各レイヤーは独自の型定義を持つ必要があります
- UI レイヤーは UI 状態のデータモデルを使用し、ドメインモデルや API モデルを直接使用してはいけません
- UI 状態モデルは API モデルではなく、ドメインモデルから導出する必要があります
- ドメインモデルは UI 状態モデルではなく、API モデルから導出する必要があります
リポジトリと DataSource のパターン
- リポジトリはデータソース間をオーケストレーションします(ドメインモデルを返します)
- データソースは生データにアクセスします(API モデルを返します)
- リポジトリは信頼できる唯一の情報源(source of truth)であるべきであり、ドメインモデルを返します
- DataSource には生の SDK/API 呼び出しのみを含める必要があります。マッピングやビジネスロジックは禁止です。
- DataSource の外でバックエンドの SDK/API を直接呼び出してはいけません
データフロー
UI Event → BLoC (emit Loading) → Repository → DataSource (API/SDK)
↓
Response → Repository (map to Domain Entity) → BLoC (emit Success/Error) → UI
Dart 3 の言語機能
- Sealed クラス: ドメインの失敗には
sealed classを使用し、レイヤー間で網羅的なパターンマッチングを可能にします。 - レコード: 完全なクラスを定義するには過剰な軽量な複数値の返却にはレコードを使用します(例:
(String name, int age))。 - If-Case パターン: null チェックとバインディングには
if (value != null)よりもif (value case final v?)を優先します。 - クラス修飾子: API の意図を表現するために
final、interface、base、sealedのクラス修飾子を使用します。
エラーハンドリング
- 関数型エラーハンドリング:
Either<Failure, T>またはResult<T>の sealed クラスを使用します。レイヤー境界を越えて例外をスローしてはいけません。 - パターンマッチング: UI と BLoC では Dart 3.x の
switch式を使用して、すべての sealed クラスの状態を網羅的に処理します。 - 必要に応じてエラーをスローし、適切な境界でキャッチします
- コンテキストを付けてエラーをログに記録します
- UI ではユーザーフレンドリーなエラーメッセージを表示します
- サイレント障害を避け、常にエラーを処理または伝播します
プラットフォームチャネルとネイティブ統合
- ネイティブコードへの単発の呼び出しには
MethodChannelを使用します(例: デバイス情報の読み取り、ネイティブ API の呼び出し) - ネイティブから Flutter への連続的なストリームには
EventChannelを使用します(例: センサーデータ、接続状態の変化) - すべてのチャネルコードは、該当する機能内の専用の
platform/ディレクトリに配置します - チャネル名は定数として定義します:
static const channel = MethodChannel('com.app.feature/method') - すべてのチャネル呼び出しを DataSource でラップします。BLoC や UI から
MethodChannelを直接呼び出してはいけません - チャネルを実装していないプラットフォームでは、
MissingPluginExceptionを適切に処理します - プラットフォーム固有の動作をガードするために
defaultTargetPlatformチェックを使用します
FFI (Foreign Function Interface)
- パフォーマンスが重要なネイティブ C/C++ コードには
dart:ffiを使用してください - バインディングは、明確なドキュメントを備えた別のクラスで定義してください
- 複数のパッケージ間でネイティブコードを共有する場合は、Federated Plugins を優先してください
プラットフォーム固有のコード
- 実行時のチェックには
Platform.isAndroid/Platform.isIOSを使用してください(dart:ioをインポート) - Web の場合は、
package:flutter/foundation.dartのkIsWebを使用してください - 可能な限り、手動のプラットフォームチェックよりもアダプティブウィジェット(
Switch.adaptive、Slider.adaptive)を優先してください
コーディングガイドラインとメンテナンス
- 簡潔さ: ファイルは 300 行未満、関数は 50 行未満に抑えてください。クラスのパブリックメソッドは 10 個未満に抑えてください。
- 強い型付け:
dynamicは厳格に禁止します。Object?または明示的な型を使用してください。 - ガード句: ネストを減らし可読性を向上させるために、早期リターン(例:
if (user == null) return;)を使用してください。 - 破棄可能なライフサイクル:
TextEditingController、ScrollController、FocusNode、StreamSubscription、AnimationControllerなどは、initState()でlate初期化し、dispose()で破棄する MUST があります。 - print 文の禁止:
print()は厳格に禁止します。すべてのログにはAppLoggerを使用してください。 - 再利用: 複数回使用されるウィジェットやコードブロックは、
core/views/widgetsまたはユーティリティに抽出してください。
ドキュメント
- What ではなく Why: コメントは MUST コードの動作ではなく、その根拠(意図)を説明する必要があります。
- パブリック API: パブリックなクラスとメソッドは、トリプルスラッシュ(
///)コメントでドキュメント化してください。 - 履歴: バージョン履歴や「fixed by」コメントを含めないでください。Git が信頼できる唯一の情報源です。
ワークフロー: 新機能の実装
アプリケーションに新機能を追加する際は、この順序に従ったワークフローに沿って進めてください。進捗を追跡するにはチェックリストをコピーしてください。
タスクの進捗
- ステップ 1: ドメインモデルを定義する。 Dart Mappable を使用して、機能の不変データクラスを作成します。
- ステップ 2: DataSource を実装する。 生の SDK/API 呼び出し(Dio、Isar、Firebase)を処理する DataSource クラスを作成または更新します。
- ステップ 3: Repository を実装する。 DataSource を消費し、純粋なドメインモデルを返す Repository を作成します。
- ステップ 4: BLoC/Cubit を実装する。 状態管理を作成します。必要な Repository を注入します。不変の UI 状態を公開します。
- ステップ 5: UI を実装する。 View ウィジェットを作成します。
BlocBuilderまたはBlocConsumerを使用して状態の変化を監視します。 - ステップ 6: 依存関係を注入する。 新しい DataSource、Repository、BLoC を依存性注入コンテナ(
injectable)に登録します。
コメント
Markdown 対応