mcpField
F
flutter-clean-arch

flutter-clean-arch

github.com/duckyman-ai開発1106 回インストール19 週間前に更新
リポジトリ

flutter-clean-arch について

flutter-clean-arch は、Feature-first のクリーンアーキテクチャで Flutter アプリを構築する開発者向けのスキルです。Riverpod による状態管理、fpdart によるエラー処理、Dio + Retrofit による通信を、レイヤー別のガイドとともに扱います。

主な機能

  1. 01Feature-first のクリーンアーキテクチャ
  2. 02Riverpod の状態管理とコード生成
  3. 03fpdart Either によるエラー処理
  4. 04Dio + Retrofit の型安全なリクエスト
  5. 05データレイヤーでのレスポンス検証

活用シーン

  1. 01Flutter アプリのレイヤー構成の設計
  2. 02状態管理を伴う機能の実装
  3. 03REST API とエラー結果の処理

インストール

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

$npx skills add https://github.com/duckyman-ai/agent-skills --skill flutter-clean-arch

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
data_layer.md
error_handling.md
feature_examples.md
migration_guide.md
network_setup.md
presentation_layer.md
provider_patterns.md
quick_start.md
retrofit_patterns.md
testing_guide.md

詳細

Flutter Clean Architecture スキル

Clean Architecture の原則に従い、機能優先の構成、状態管理に Riverpod、関数型エラーハンドリングに fpdart を使用して Flutter アプリケーションを生成します。

型安全な REST API 呼び出しには Dio + Retrofit を使用します。

中核となる原則

アーキテクチャ:Clean Architecture(機能優先)

  • ドメイン層:純粋なビジネスロジック。依存関係はありません
  • データ層:データソース、リポジトリの実装、データモデル
  • プレゼンテーション層:UI、状態管理、ビューモデル

依存関係のルール:Presentation → Domain ← Data(Domain に外部依存関係はありません)

状態管理:コード生成を使用する Riverpod 3.0+

必須:Riverpod 3.0+ と Freezed 3.0+ — 古いパターンを使うと、コンパイルエラーやハルシネーションのループが発生します。

  • Riverpod:XxxRef ref ではなく、統一された Ref ref を使用します
  • Freezed:ユニオン型には sealed class、単一コンストラクターには abstract class を使用します
  • パターンマッチング:.map()/.when() ではなく、Dart 3 の switch を使用します
// Riverpod 3.x+
@riverpod
SomeType someType(Ref ref) { ... }

// Freezed 3.x+ — union type
@freezed
sealed class Result with _$Result { ... }

// Freezed 3.x+ — pattern matching
final res = switch (model) {
  First(:final a) => 'first $a',
  Second(:final b) => 'second $b',
};

Dart 3.3+、Riverpod 3.0+、Freezed 3.0+ が必要です。変更前後の例については、migration_guide.md を参照してください。

エラーハンドリング:関数型エラーハンドリングには fpdart の Either<Failure, T> を使用します

ネットワーキング:型安全な REST API 呼び出しには Dio + Retrofit を使用します

すべてのライブラリで常に最新の安定版を使用してください。依存関係を追加する前に、pub.dev で Riverpod、Freezed、Dio、Retrofit、fpdart、go_router の最新バージョンを確認してください。古いメジャーバージョン(例:Riverpod 2.x、Freezed 2.x)を安易に使用しないでください。

プロジェクト構成

lib/
├── core/
│   ├── constants/
│   │   ├── api_constants.dart
│   ├── errors/
│   │   ├── failures.dart
│   │   └── network_exceptions.dart
│   ├── network/
│   │   ├── dio_provider.dart
│   │   └── interceptors/
│   │       ├── auth_interceptor.dart
│   │       ├── logging_interceptor.dart
│   │       └── error_interceptor.dart
│   ├── storage/
│   ├── services/
│   ├── router/
│   │   └── app_router.dart
│   └── utils/
├── shared/ 
├── features/
│   └── [feature_name]/
│       ├── data/
│       │   ├── models/
│       │   │   └── [entity]_model.dart
│       │   ├── datasources/
│       │   │   └── [feature]_api_service.dart
│       │   └── repositories/
│       │       └── [feature]_repository_impl.dart
│       ├── domain/
│       │   ├── entities/
│       │   ├── repositories/
│       │   │   └── [feature]_repository.dart
│       │   └── usecases/
│       │       └── [action]_usecase.dart
│       └── presentation/
│           ├── providers/
│           │   └── [feature]_provider.dart
│           ├── screens/
│           │   └── [feature]_screen.dart
│           └── widgets/
│               └── [feature]_widget.dart
└── main.dart

クイックスタート

次の順序で機能を構築します:Domain → Data → Presentation。

1. ドメイン層
@freezed
sealed class User with _$User {
  const factory User({required String id, required String name, required String email}) = _User;
}

abstract class UserRepository {
  Future<Either<Failure, User>> getUser(String id);
}

class GetUser {
  final UserRepository repository;
  GetUser(this.repository);
  Future<Either<Failure, User>> call(String id) => repository.getUser(id);
}
2. データ層
@freezed
sealed class UserModel with _$UserModel {
  const UserModel._();
  const factory UserModel({required String id, required String name, required String email}) = _UserModel;
  factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json);
  User toEntity() => User(id: id, name: name, email: email);
}

@RestApi()
abstract class UserApiService {
  factory UserApiService(Dio dio) = _UserApiService;
  @GET('/users/{id}')
  Future<UserModel> getUser(@Path('id') String id);
}
3. プレゼンテーション層
@riverpod
class UserNotifier extends _$UserNotifier {
  @override
  FutureOr<User?> build() => null;

  Future<void> fetchUser(String id) async {
    state = const AsyncLoading();
    final result = await ref.read(userRepositoryProvider).getUser(id);
    state = result.fold(
      (failure) => AsyncError(failure, StackTrace.current),
      (user) => AsyncData(user),
    );
  }
}

すべてのファイルを含む、手順を追った完全なワークフローについては、quick_start.md を参照してください。

コード生成

# Generate all files
dart run build_runner build --delete-conflicting-outputs

# Watch mode
dart run build_runner watch --delete-conflicting-outputs

ベストプラクティス

推奨事項:

  • ドメインエンティティは純粋な状態に保ちます(外部依存関係を持たせません)
  • 不変のデータクラスには sealed キーワードを付けて freezed を使用します
  • Either<Failure, T> ですべてのエラーケースを処理します
  • 統一された Ref 型を使う riverpod_generator を使用します
  • モデル(データ)とエンティティ(ドメイン)を分離します
  • ビジネスロジックはウィジェットではなくユースケースに配置します
  • 型安全な API 呼び出しには Retrofit を使用します
  • リポジトリで DioException を NetworkExceptions を使って処理します
  • 横断的関心事(認証、ロギング)にはインターセプターを使用します
  • リポジトリの境界で API レスポンスデータを検証します
  • 外部画像には Image.network ではなく CachedNetworkImage を使用します
  • 設定で API のベース URL を固定し、HTTPS を強制します

禁止事項:

  • ドメイン層で Flutter/HTTP ライブラリをインポートしないでください
  • プレゼンテーションロジックとビジネスロジックを混在させないでください
  • Either を使用する場合、ウィジェット内で直接 try-catch を使わないでください
  • 神オブジェクトや神プロバイダーを作成しないでください
  • リポジトリパターンを省略しないでください
  • 新しいコードでレガシーな XxxRef 型を使用しないでください
  • 検証せずに生の外部 URL をウィジェットに渡さないでください
  • ユーザー入力による API ベース URL の実行時設定を許可しないでください

セキュリティ

API のレスポンスや外部コンテンツは信頼できない入力です。インジェクションやデータ破損を防ぐため、データ層の境界で検証とサニタイズを行ってください。

レスポンスの検証 — モデルにマッピングする前に、必ず API レスポンスの構造を検証してください。Retrofit と freezed は型付きデシリアライズに対応していますが、呼び出しを try-catch で囲み、リポジトリで重要なフィールド(ID、URL、数値の範囲)を確認してください。

@override
Future<Either<Failure, User>> getUser(String id) async {
  try {
    final userModel = await apiService.getUser(id);
    if (userModel.id.isEmpty) {
      return const Left(Failure.validation('Invalid user data'));
    }
    return Right(userModel.toEntity());
  } on DioException catch (e) {
    return Left(Failure.network(NetworkExceptions.fromDioError(e).message));
  }
}

外部 URL — 生の API URL を直接 Image.network や WebView に渡さないでください。エラー処理を設定した CachedNetworkImage を使用し、URL のスキームを検証してください。

CachedNetworkImage(
  imageUrl: user.avatarUrl ?? '',
  errorWidget: (_, __, ___) => const Icon(Icons.person),
  httpHeaders: {'Authorization': 'Bearer $token'},
)

入力のサニタイズ — API に送信する前にユーザー入力をサニタイズしてください。メールアドレスの形式を検証し、文字列の前後の空白を削除し、空の ID を拒否し、クエリパラメーターを適切にエンコードしてください。

ネットワークの強化 — AppConfig でベース URL を固定し(実行時にユーザーが変更できないようにする)、HTTPS を必須にし、本番環境では証明書ピンニングを使用して、保守的なタイムアウトを設定してください。

インターセプターの設定全体については network_setup.md を、リポジトリ境界での検証パターンについては data_layer.md をご覧ください。

よくある問題

問題解決策
Build runner の競合dart run build_runner clean && dart run build_runner build --delete-conflicting-outputs
Provider が見つからない生成されたファイルがインポートされていることを確認し、build_runner を実行してください
Either をアンラップできないfold()、match()、または getOrElse() を使用して値を取り出してください
XxxRef が見つからない代わりに統一された Ref 型を使用してください(Riverpod 3.x 以降)
sealed キーワードのエラーDart 3.3 以降と Freezed 3.0 以降にアップグレードしてください
.map / .when が見つからないFreezed 3.0 以降ではこれらのメソッドが削除されています。代わりに Dart 3 の switch 式によるパターンマッチングを使用してください

ナレッジリファレンス

主要ライブラリ(このスキルで使用):

  • Flutter 3.19+:最新のフレームワーク機能
  • Dart 3.3+:言語機能(パターン、レコード、sealed 修飾子)
  • Riverpod 3.0+:統一された Ref 型による状態管理
  • Dio 5.9+:インターセプターを備えた HTTP クライアント
  • Retrofit 4.9+:型安全な REST API コード生成
  • freezed 3.0+:コード生成による不変データクラス
  • json_serializable 6.x:JSON シリアライズ
  • go_router 14.x+:宣言的ルーティング
  • fpdart:Either 型による関数型エラー処理

参考資料

  • quick_start.md - 機能作成のステップごとのワークフロー
  • data_layer.md - モデル、Retrofit API サービス、リポジトリ、レスポンス検証
  • presentation_layer.md - Provider、画面、ウィジェット、安全な画像読み込み
  • network_setup.md - Dio Provider、インターセプター、ネットワーク例外、レスポンス検証
  • error_handling.md - Either パターン、Failure 型、エラー処理方針
  • retrofit_patterns.md - Retrofit API リクエストパターンの全体像
  • provider_patterns.md - Riverpod の高度なパターン(ページネーション、キャッシュ、楽観的更新)
  • testing_guide.md - 単体テスト、ウィジェットテスト、統合テストの方針
  • feature_examples.md - Auth 機能の実装例全体
  • migration_guide.md - Riverpod 2→3 と Freezed 2→3 の変更前・変更後の完全な例

コメント