mcpField
F
flutter-bloc-development

flutter-bloc-development

github.com/abdelhakrazi開発20354 回インストール39 週間前に更新
リポジトリ

flutter-bloc-development について

flutter-bloc-developmentは、Flutterコードベース向けにBLoCによる状態管理、機能単位のレイヤー構成、Repositoryを介したデータアクセス、デザインシステム定数の使用を定めるスキルです。機能構成からUIとデータフローまでを扱います。

主な機能

  1. 01機能単位のプロジェクト構成
  2. 02BLoCのイベントと状態パターン
  3. 03Repositoryとデータソースの分離
  4. 04UIでのデザインシステム定数の使用
  5. 05ローディング、成功、エラーの状態遷移

活用シーン

  1. 01新しいFlutter機能の実装
  2. 02ウィジェットからビジネスロジックを分離
  3. 03バックエンド呼び出しをデータソースに集約
  4. 04UIのハードコードされたスタイル値の置き換え

インストール

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

$npx skills add https://github.com/abdelhakrazi/flutter-bloc-clean-architecture-skill --skill flutter-bloc-development

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

同梱ファイル

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

LICENSE.txt
README.md
examples/earnings/
earnings.dart
examples/earnings/bloc/
earnings_bloc.dart
earnings_state.dart
earnings_event.dart
examples/earnings/data/
data.dart
examples/earnings/data/datasources/
earnings_datasource.dart
examples/earnings/data/models/
earnings_summary.dart
examples/earnings/data/repositories/
earnings_repository.dart
examples/earnings/view/
earnings_page.dart
view.dart
examples/earnings/view/widgets/
daily_earnings_list.dart
earnings_summary_card.dart
examples/shared/data/
data.dart
examples/shared/data/datasources/
api_client.dart
user_datasource.dart
examples/shared/data/models/
api_response.dart
user.dart
examples/shared/widgets/
trip_summary_card.dart

詳細

Flutter BLoC 開発

このスキルは、このコードベースでのすべての Flutter 開発において、BLoC による状態管理、厳格なレイヤー分離、デザインシステム定数の必須使用を徹底します。

判断ツリー:アプローチの選択

User task → What are they building?
    │
    ├─ New screen/feature → Full feature implementation:
    │   1. Create feature folder (lib/[feature]/)
    │   2. Define BLoC (bloc/[feature]_event.dart, _state.dart, _bloc.dart)
    │   3. Create data layer (data/datasources/, data/repositories/, data/models/)
    │   4. Build UI (view/[feature]_page.dart, view/widgets/)
    │   5. Create barrel files ([feature].dart, data/data.dart, view/view.dart)
    │
    ├─ New widget only → Presentation layer:
    │   1. Feature-specific: feature/view/widgets/
    │   2. Shared/reusable: shared/widgets/
    │   3. Use design system constants (NO hardcoded values)
    │   4. Connect to existing BLoC if needed
    │
    ├─ Data integration → Data layer only:
    │   1. Create datasource (feature/data/datasources/)
    │   2. Create repository (feature/data/repositories/)
    │   3. Wire up in existing or new BLoC
    │
    └─ Refactoring → Identify violations:
        1. Check for hardcoded colors/spacing/typography
        2. Check for business logic in UI
        3. Check for direct SDK calls outside datasources
        4. Check for missing Loading state before async operations
        5. Check for missing Equatable on Events/States
        6. Check for improper error handling (use SnackBar + AppColors.error)

アーキテクチャの概要

フィーチャー優先の構成(公式の BLoC 推奨):

lib/
├── [feature]/                    # Feature folder (e.g., earnings/, auth/, trips/)
│   ├── bloc/
│   │   ├── [feature]_bloc.dart
│   │   ├── [feature]_event.dart
│   │   └── [feature]_state.dart
│   ├── data/
│   │   ├── datasources/          # Feature-specific API calls
│   │   ├── repositories/         # Data orchestration
│   │   ├── models/               # Feature-specific DTOs
│   │   └── data.dart             # Data layer barrel file
│   ├── view/
│   │   ├── [feature]_page.dart   # Main screen
│   │   ├── widgets/              # Feature-specific widgets
│   │   └── view.dart             # View barrel file
│   └── [feature].dart            # Feature barrel file
├── shared/                       # Cross-feature code
│   ├── data/
│   │   ├── datasources/          # Shared API clients (ApiClient, UserDataSource)
│   │   ├── models/               # Shared models (User, ApiResponse)
│   │   └── data.dart             # Shared data barrel file
│   ├── widgets/                  # Reusable UI components
│   └── utils/                    # Design system (colors, spacing, typography)
└── app.dart                      # App entry point
フィーチャーと共有データの使い分け
シナリオ配置場所例
1 つのフィーチャーで使用する API エンドポイントfeature/data/EarningsDataSource → /api/earnings/...
複数のフィーチャーで使用する API クライアント/サービスshared/data/ApiClient、UserDataSource
1 つのフィーチャーで使用するモデルfeature/data/models/EarningsSummary
複数のフィーチャーで使用するモデルshared/data/models/User、ApiResponse

バレルファイル — レイヤーごとに 1 つのインポート:

// Feature barrel: earnings/earnings.dart
export 'bloc/earnings_bloc.dart';
export 'bloc/earnings_event.dart';
export 'bloc/earnings_state.dart';
export 'data/data.dart';
export 'view/view.dart';

// Data layer barrel: earnings/data/data.dart
export 'datasources/earnings_datasource.dart';
export 'repositories/earnings_repository.dart';
export 'models/earnings_summary.dart';

// Shared data barrel: shared/data/data.dart
export 'datasources/api_client.dart';
export 'datasources/user_datasource.dart';
export 'models/user.dart';

主なルール:

  • すべての状態変更は BLoC を経由します
  • データソース以外でバックエンド SDK を直接呼び出しません
  • ハードコードされた値は一切使用しません(色、間隔、タイポグラフィ)
  • すべてのデータアクセスにリポジトリパターンを使用します
  • フィーチャー固有のコードはフィーチャーフォルダーに配置します
  • 共有コード(2 つ以上のフィーチャーで使用)は shared/ に配置します

BLoC の実装

イベント → 状態 → BLoC(フィーチャーごとに 3 ファイル)

イベント — ユーザー操作とシステムによるトリガー:

abstract class FeatureEvent extends Equatable {
  const FeatureEvent();
  @override
  List<Object?> get props => [];
}

class FeatureActionRequested extends FeatureEvent {
  final String param;
  const FeatureActionRequested({required this.param});
  @override
  List<Object> get props => [param];
}

状態 — UI で起こりうるすべての状態:

abstract class FeatureState extends Equatable {
  const FeatureState();
  @override
  List<Object?> get props => [];
}

class FeatureInitial extends FeatureState {}
class FeatureLoading extends FeatureState {}

class FeatureSuccess extends FeatureState {
  final DataType data;
  const FeatureSuccess(this.data);
  @override
  List<Object> get props => [data];
}

class FeatureError extends FeatureState {
  final String message;
  const FeatureError(this.message);
  @override
  List<Object> get props => [message];
}

BLoC — Loading → Success/Error パターンによるイベントハンドラー:

class FeatureBloc extends Bloc<FeatureEvent, FeatureState> {
  final FeatureRepository _repository;

  FeatureBloc({required FeatureRepository repository})
      : _repository = repository,
        super(FeatureInitial()) {
    on<FeatureActionRequested>(_onActionRequested);
  }

  Future<void> _onActionRequested(
    FeatureActionRequested event,
    Emitter<FeatureState> emit,
  ) async {
    emit(FeatureLoading());
    try {
      final result = await _repository.doSomething(event.param);
      emit(FeatureSuccess(result));
    } catch (e) {
      emit(FeatureError(e.toString()));
    }
  }
}

重要:非同期処理の前に必ず Loading を発行し、その後 Success または Error を発行します。ローディング状態を省略しないでください。


データレイヤー

データフロー:

UI Event → BLoC (emit Loading) → Repository → Datasource (SDK)
    ↓
Response → Repository (map to entity) → BLoC (emit Success/Error) → UI

データソース — バックエンド SDK の呼び出しのみ:

class FeatureDataSource {
  final SupabaseClient _supabase;
  FeatureDataSource(this._supabase);

  Future<Map<String, dynamic>> fetch() async {
    return await _supabase.from('table').select().single();
  }
}

リポジトリ — オーケストレーションとマッピング:

class FeatureRepository {
  final FeatureDataSource _dataSource;
  FeatureRepository(this._dataSource);

  Future<DomainEntity> fetchData() async {
    final response = await _dataSource.fetch();
    return DomainEntity.fromJson(response);
  }
}

デザインシステム(必須)

色

✅ AppColors.primary、AppColors.error、AppColors.textPrimary ❌ Color(0xFF...)、Colors.blue、インラインの 16 進数値

間隔

✅ AppSpacing.xs (4)、AppSpacing.sm (8)、AppSpacing.md (16)、AppSpacing.lg (24)、AppSpacing.xl (32) ✅ AppSpacing.screenHorizontal (24)、AppSpacing.screenVertical (16) ❌ EdgeInsets.all(16.0)、ハードコードされたパディング値

角丸

✅ AppRadius.sm (8)、AppRadius.md (12)、AppRadius.lg (16)、AppRadius.xl (24) ❌ BorderRadius.circular(12)、インラインの角丸値

タイポグラフィ

✅ AppTypography.headlineLarge、AppTypography.bodyMedium、theme.textTheme.bodyMedium ❌ TextStyle(fontSize: 16)、インラインのテキストスタイル


UI パターン

画面テンプレート
GradientScaffold(
  body: SafeArea(
    child: Column(
      children: [
        Padding(
          padding: const EdgeInsets.all(AppSpacing.screenHorizontal),
          child: HeaderWidget(),
        ),
        Expanded(
          child: SingleChildScrollView(
            padding: const EdgeInsets.symmetric(horizontal: AppSpacing.screenHorizontal),
            child: ContentWidget(),
          ),
        ),
        Padding(
          padding: const EdgeInsets.all(AppSpacing.screenHorizontal),
          child: ActionButton(
            onPressed: () => context.read<FeatureBloc>().add(ActionEvent()),
          ),
        ),
      ],
    ),
  ),
)
BLoC Consumer パターン
BlocConsumer<FeatureBloc, FeatureState>(
  listener: (context, state) {
    if (state is FeatureError) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.message), backgroundColor: AppColors.error),
      );
    }
  },
  builder: (context, state) {
    if (state is FeatureLoading) return const Center(child: CircularProgressIndicator());
    if (state is FeatureSuccess) return SuccessWidget(data: state.data);
    return const SizedBox.shrink();
  },
)

よくある落とし穴

❌ ウィジェット内のビジネスロジック → BLoC に移動します ❌ リポジトリ内での Supabase/Firebase の直接呼び出し → データソースに移動します ❌ 非同期処理前のローディング状態の省略 → 必ず最初に Loading を発行します ❌ Color(0xFF4A90A4) のようなハードコードされた色 → AppColors.primary を使用します ❌ padding: 16 のようなマジックナンバー → AppSpacing.md を使用します


クイックリファレンス

操作パターン
イベントをディスパッチするcontext.read<Bloc>().add(Event())
インラインで状態を監視するcontext.watch<Bloc>().state
監視 + ビルドBlocConsumer
監視のみBlocListener
ビルドのみBlocBuilder

提出前のチェックリスト

  • Events/States/BLoC で Equatable を使用している
  • すべての非同期処理が Loading → Success/Error になっている
  • UI にビジネスロジックがない
  • データソース以外に SDK 呼び出しがない
  • 色/間隔/タイポグラフィが一切ハードコードされていない
  • エラー処理で AppColors.error を使った SnackBar を表示する
  • dart format でコードをフォーマットしている

コメント