bloc について
Dart と Flutter アプリで Bloc 状態管理パターンを使うための開発・テスト用ワークフロースキルです。アーキテクチャ、イベントと状態、ウィジェット連携に加え、`bloc_test` と `mocktail` によるテストの規約を示します。
主な機能
- 01Bloc と Cubit の設計指針
- 02Page と View の分離
- 03イベントと状態の規約
- 04ウィジェット連携パターン
- 05`bloc_test` と `mocktail` によるテスト
活用シーン
- 01Bloc または Cubit でビジネスロジックを構成
- 02プレゼンテーション、ロジック、データ層を分離
- 03状態を Flutter ウィジェットに接続
- 04Bloc と Cubit のテストを作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ verygoodopensource/ vgv-ai-flutter-plugin --skill blocインストール後、Claude Code で/blocを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlreferences/architecture.mdpatterns.mdtesting.mdwidgets.md詳細
Bloc
BLoC(Business Logic Component)パターンを使用し、ビジネスロジックとプレゼンテーション層を分離する、Dart および Flutter 向けの状態管理ライブラリです。
コア標準
すべての Bloc/Cubit の作業に、以下の標準を適用してください。
- すべての Bloc および Cubit のテストには
package:bloc_testのblocTest()を使用してください — 手動でストリームをアサートする生のtest()は使用しないでください - モックには
package:mocktailを使用してください —package:mockitoは使用しないでください build:でテスト対象の Bloc を構築します — コールバックから新しいインスタンスを返してください。setUpで構築して共有してはいけません。これにより、各blocTestはクリーンな Bloc から開始し、そのライフサイクルもblocTestによって管理されます- Bloc 間で直接依存しないでください — Bloc 同士は UI または共有リポジトリを通じて連携します
- Page/View を分離します — Page は
BlocProviderを通じて Bloc/Cubit を提供し、View はBlocBuilder/BlocListenerを通じて利用します - イベントと複数の状態型には sealed クラスを使用します — Dart 3 の
switchによる網羅的なパターンマッチングが可能になります - すべての状態とイベントに Equatable を使用します —
Equatableを継承し、値の等価性のためにpropsをオーバーライドします - ビジネスロジックは Bloc/Cubit のみに記述します — ウィジェット、Page、View には記述しないでください
- 単一責任を守ります — 機能ごとに Bloc/Cubit を 1 つ使用します
emitはハンドラー内でのみ呼び出します —on<Event>コールバックまたは Cubit メソッドから呼び出してください。ストリームサブスクリプションや、ハンドラーの終了後も存続するコールバックから呼び出してはいけません- Dart 3.13 のプライマリコンストラクター — Dart 3.13 以降をベースラインとする場合は、イベント、状態、ウィジェットのフィールドをプライマリコンストラクターの宣言パラメーターとして宣言してください(
class const ProfileRequested(final String userId) extends ProfileEvent)。this.fieldを使う従来の形式は、3.13 未満の場合にのみ使用してください
Cubit と Bloc の比較
| 項目 | Cubit | Bloc |
|---|---|---|
| API | 関数 → emit(state) | イベント → on<Event> → emit(state) |
| 複雑さ | 低い | 高い |
| 追跡可能性 | 低い(イベントログなし) | 完全(イベント+状態遷移) |
| 用途 | シンプルな状態、UI 主導のロジック | 複雑なフロー、イベント駆動、変換 |
| テスト | メソッドを呼び出し、状態をアサート | イベントを追加し、状態をアサート |
Cubit の例
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
Bloc の例
sealed class CounterEvent extends Equatable {
const CounterEvent();
@override
List<Object> get props => [];
}
final class CounterIncrementPressed extends CounterEvent {}
final class CounterDecrementPressed extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) => emit(state + 1));
on<CounterDecrementPressed>((event, emit) => emit(state - 1));
}
}
命名規則
イベント
パターン: BlocSubject + Noun + VerbPastTense
| イベントクラス名 | 意味 |
|---|---|
TodoListSubscriptionRequested | todo リストのストリームを購読する |
TodoListTodoDeleted | 特定の todo を削除する |
TodoListUndoDeletionRequested | 直前の削除を取り消す |
LoginFormSubmitted | ログインフォームを送信する |
ProfilePageRefreshed | プロフィールページを更新する |
sealed class TodoListEvent extends Equatable {
const TodoListEvent();
@override
List<Object> get props => [];
}
final class TodoListSubscriptionRequested extends TodoListEvent {}
final class const TodoListTodoDeleted({required final Todo todo})
extends TodoListEvent {
@override
List<Object> get props => [todo];
}
状態
サブクラス方式(複数の状態型)
各状態が異なるデータを持つ場合に使用します。
| 状態クラス名 | 意味 |
|---|---|
LoginInitial | まだ何も実行されていない状態 |
LoginInProgress | ログインリクエスト実行中 |
LoginSuccess | ログイン成功 |
LoginFailure | ログイン失敗 |
sealed class LoginState extends Equatable {
const LoginState();
@override
List<Object> get props => [];
}
final class LoginInitial extends LoginState {}
final class LoginInProgress extends LoginState {}
final class const LoginSuccess({required final User user}) extends LoginState {
@override
List<Object> get props => [user];
}
final class const LoginFailure({required final String error})
extends LoginState {
@override
List<Object> get props => [error];
}
単一クラス方式(1 つの状態と複数のフィールド)
すべての状態でデータの形が共通する場合に使用します。status 列挙型とデータフィールドを持つ 1 つのクラスを用意し、状態遷移には copyWith を使用します。完全な構成については、references/patterns.md を参照してください。
アーキテクチャ
| レイヤー | 含まれるもの | 依存先 |
|---|---|---|
| プレゼンテーション | ページ、ビュー、ウィジェット | ビジネスロジック |
| ビジネスロジック | Bloc、Cubit | データ |
| データ | リポジトリ、データプロバイダー | 外部ソース |
データレイヤー
リポジトリはデータソースを抽象化し、Bloc/Cubit にクリーンな API を提供します。すべてのテストファイルについて、test/ 内に機能フォルダー構造を再現してください。
リポジトリの例、機能フォルダー構造、テストディレクトリのレイアウトについては、references/architecture.md を参照してください。
Flutter ウィジェット
BlocProvider を使って Bloc または Cubit をサブツリーに提供し、BlocBuilder、
BlocListener、BlocConsumer、または BlocSelector で利用します。再ビルドを状態全体ではなく特定のフィールドに依存させたい場合は BlocSelector を、ナビゲーションやスナックバーなどの副作用には BlocListener
を使用します。
重要なルールは次のとおりです。コールバック(onPressed、onTap)では context.read、build 内では context.watch または BlocBuilder を使います。build メソッドの外で context.watch を呼び出してはいけません。
追加リソース
- references/architecture.md — リポジトリの例、機能フォルダー構造、テストディレクトリのレイアウト
- references/widgets.md — ウィジェットと context 拡張の一覧、Page/View パターン、
BlocListenerの例 - references/testing.md —
blocTest()のパラメーター、Cubit/Bloc のテスト例、依存関係のモック、ウィジェット統合テスト - references/patterns.md — 単一クラスの状態、Bloc/Cubit を使った機能の追加、非同期処理、イベントトランスフォーマー