BLoC(Business Logic Component)— Googleが2018年のDartConfで発表したFlutter用の状態管理パターンです。BLoCはリアクティブストリーム(Stream)を介してビジネスロジックとユーザーインターフェースを分離します。UIがEventを送信し、BLoCがそれを処理してStream経由で新しいStateを返します。pub.devによると、flutter_blocパッケージは11,000以上のいいねを獲得し、何千ものFlutterアプリケーションで使用されています。
主なポイント
BLoC(Business Logic Component)— Flutter用のアーキテクチャパターンで、ビジネスロジックをUIから分離した独立したクラスに抽出します。BLoCはイベントのストリーム(Event)を介して入力データを受け取り、状態のストリーム(State)を介して出力データを生成します。プレゼンテーション層(Widget)はStateストリームにサブスクライブしてUIをレンダリングするだけで、ビジネスロジックを直接実行することはありません。
BLoCの概念はリアクティブプログラミングとObserverパターンに基づいています。各BLoCコンポーネントは、明確な契約を持つ独立したモジュールです。既知のEventのセット(何が起こり得るか)と既知のStateのセット(何が表示され得るか)があります。開発者はUIから「誤って」状態を変更することはできません — 特定のEventを介してのみ可能です。これにより、コードは予測可能でテスト可能になります。
Flutter Community 2025の調査によると、BLoCはProviderに次いでFlutterの状態管理ソリューションで人気第2位です。主な利点:強い型付け、ロジックの分離、組み込みのStreamサポート、豊富なユーティリティエコシステム(BlocProvider、BlocListener、BlocSelector)。
BLoCアーキテクチャは3つのエンティティを中心に構築されています:Event(入力)、Bloc(ハンドラ)、State(出力)。Widgetはadd()メソッドを介してEventを送信します。BlocはmapEventToStateまたはon<Event>メソッドでEventを受け取り、ビジネスロジックを実行し、yieldを介して新しいStateを発行します。WidgetはStreamを介してStateを受け取り、再構築します。
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
@override
Stream<int> mapEventToState(CounterEvent event) async* {
if (event is Increment) {
yield state + 1;
} else if (event is Decrement) {
yield state - 1;
}
}
}タイプセーフティ: BlocはEventとStateの2つの型でパラメータ化されています。Dartコンパイラは、Widgetが宣言されたEventのみを呼び出し、Blocが宣言されたStateのみを返すことをチェックします。「不明なアクション」のような実行時エラーは排除されます。
CloseとDispose: BlocはCloseableインターフェースを実装します。ウィジェットが破棄されると、Blocは自動的にclose()メソッドを介してStreamを閉じます。リアクティブサブスクリプションのリークは不可能です — BlocProviderがBlocのライフサイクルを管理し、ルートまたはページにバインドします。
Cubitは、flutter_bloc 6.0パッケージで導入された、EventなしのBlocの簡略化された実装です。CubitはEventクラスの代わりに直接メソッドを宣言します:increment()、fetchData()。内部的には、Cubitは同じStreamベースのメカニズムを使用しますが、Event層を隠します。これにより、単純なシナリオではボイラープレートが40〜50%削減されます。
| 特性 | Bloc | Cubit |
|---|---|---|
| Eventクラス | 必須 | 不要 |
| ボイラープレート | 高い | 低い |
| アクション追跡 | Eventタイプ経由 | メソッド名のみ |
| 最適な用途 | 複雑なシナリオ | 単純な状態 |
| 分析 | Eventによる自動 | 手動 |
Cubitを選ぶ場合: 2〜3のバリアントがある状態(loading、loaded、error)、単純なフォーム、カウンター、UI状態(開く/閉じる)。Blocを選ぶ場合: 複数のアクションを持つ複雑なビジネスロジック:注文処理、認証、データ同期。BlocはEventを通じて各アクションの詳細なトレースを提供します — すべての呼び出しはBlocObserverに記録されます。
BlocObserver — アプリケーション内のすべてのBlocとCubitを追跡するグローバルオブザーバーです。Event、State、エラー、遷移をログに記録できます。1つのインスタンスを接続するだけです:Bloc.observer = AppBlocObserver()、アプリケーション全体の状態追跡が集中管理されます。
BlocProvider — flutter_blocのInheritedWidgetで、子ウィジェットにBlocを提供します。ウィジェットが初期化されると、BlocProviderはBlocを作成し、破棄されるとclose()を介して自動的に閉じます。BlocProviderはMaterialAppレベル(グローバルBloc)または特定のルートレベル(ローカルBloc)に配置できます。
BlocProvider(
create: (context) => CounterBloc(),
child: Column(
children: [
BlocBuilder<CounterBloc, int>(
builder: (context, state) => Text('$state'),
),
ElevatedButton(
onPressed: () => context.read<CounterBloc>().add(Increment()),
child: Text('+'),
),
],
),
)BlocBuilder — 新しいStateごとにUIを再構築するウィジェット。BlocListener — 副作用用(UIを再構築せずにStateを1回処理):SnackBarを表示、別の画面に移動。BlocConsumer — 再構築と副作用の両方が必要な場合のBuilderとListenerの組み合わせ。BlocSelector — Stateの特定のフィールドが変更された場合のみ選択的に再構築します。
MultiBlocProvider — ネストレベルを増やさずにネストされたBlocProviderを使用するためのウィジェット。10〜15個のBlocを持つFlutterアプリケーションは、ルートレベルでMultiBlocProviderを使用して、アプリケーション全体で利用可能なすべてのBloc(AuthenticationBloc、CartBloc、SettingsBloc)を登録します。
BLoCのテストは、Flutterウィジェットなしで分離して行われます。Dartパッケージflutter_testとbloc_testパッケージをインポートするだけです。テストシナリオ:Blocを作成し、Eventを追加し、Stateを確認します。blocTest — build → act → expectのシーケンスを自動化するユーティリティです。
blocTest<CounterBloc, int>(
'emits [1] when Increment is added',
build: () => CounterBloc(),
act: (bloc) => bloc.add(Increment()),
expect: () => [1],
)モッキング: リポジトリまたはAPIに依存するBlocは、mocktailを介してモックでテストされます。リポジトリは抽象化レベルでモックされ、Blocはコンストラクタを介してモックされた依存関係を受け取ります。Hydrated Bloc — ローカルストレージへの自動状態保存/復元のための拡張機能。HydratedBlocStorageと一時ファイルストレージを使用してテストされます。
フォルダとファイル: BLoCを使用した典型的なFlutterプロジェクト構造:bloc/counter_bloc.dart、bloc/counter_event.dart、bloc/counter_state.dart。30画面以上の場合は、機能ベースのグループ化が推奨されます:features/auth/bloc/、features/cart/bloc/。各Blocは別ファイル、各EventとStateは別ファイルまたはBlocと同じファイルに配置します。
パフォーマンス: BLoCは空のStreamにオーバーヘッドを作成しません。BlocBuilderはbuildWhenを使用して再構築をフィルタリングします — 特定の条件が変更された場合のみウィジェットが更新されます。Closeにより、非アクティブなBlocがメモリを消費しないことが保証されます。Flutter DevToolsによると、BLoCはバンドルサイズに1%未満しか追加しません。
Providerからの移行: BLoCは同じプロジェクト内でProviderと簡単に共存できます。段階的な移行:最初に最も複雑なProviderをBlocに置き換え、次に残りを置き換えます。BlocProviderはProviderツリーと互換性があります:古いウィジェットはProviderを、新しいウィジェットはBlocProviderを、同じアプリケーション内で使用できます。
よくある質問
BLoCはビジネスロジックの分離と強い型付けにEvent + Streamを使用します。Providerは単純な依存性注入とChangeNotifierのためのInheritedWidgetのラッパーです。BLoCは複数の状態を持つ複雑なシナリオに適しており、ProviderはローカルなUI状態に適しています。BLoCはより多くのボイラープレートを必要としますが、Eventを通じて完全なトレーサビリティを提供します。
Hydrated Blocはhydrated_blocパッケージの拡張機能で、最後のStateをローカルストレージ(デフォルトではHive)に自動的に保存します。アプリケーションが再起動すると、Blocは初期状態ではなく保存された状態を復元します。これにより、手動で保存を呼び出さなくても永続性の問題が解決されます:ログイン、カート、設定がセッション間で自動的に保存されます。
BLoCのエラーは、mapEventToStateまたはon<Event>内でtry-catchを使用して処理されます。エラーが発生すると、BlocはエラーStateを返します:yield LoadError(error.message)。UIでは、BlocListenerまたはBlocConsumerがStateのエラータイプをチェックし、SnackBarまたはダイアログを表示します。BlocObserverは未処理の例外をすべてグローバルにログに記録します。
BLoCはDart StreamとFlutterウィジェットを使用するため、Flutter固有のパターンです。Event → Bloc → Stateの概念はAngularDartやServer-side Dartに適応できますが、主要なエコシステム(BlocProvider、BlocBuilder、BlocObserver)はFlutterに依存しています。React Nativeの場合はReduxまたはMobXを、SwiftUIの場合はCombine + MVVMを使用してください。
Cubit — 単純な状態(カウンター、トグル、2〜3フィールドのフォーム)向け。Bloc — 複雑なロジック(ニュースフィード、注文処理、認証)向け。主なルール:分析やデバッグのために各アクション(Event)のトレースが必要な場合はBlocを選択します。状態を変更するメソッドで十分な場合はCubitを選択します。両方のパターンは同じプロジェクト内で共存できます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。