# 2026年のFlutter状態管理:Riverpod vs Bloc vs GetX 徹底比較ガイド > Riverpod 3.0、Bloc 9.0、GetXの3大Flutter状態管理ソリューションを、コード例・パフォーマンス・テスト戦略の観点から徹底比較します。 - Published: 2026-04-02 - Updated: 2026-04-02 - Author: Anthony Fillion-Maillet - Tags: flutter, state-management, riverpod, bloc, getx - Reading time: 5 min --- Flutter状態管理は、ウィジェット間のデータフローを制御するアプリケーション設計の中核的要素である。2026年現在、Flutterエコシステムでは3つのソリューションが主流を占める。コンパイル時安全性を実現したRiverpod 3.0、エンタープライズ向けイベント追跡機能を備えたBloc 9.0、そして採用率が低下しながらも既存プロジェクトに残存するGetXである。適切なソリューションの選択は、テスト容易性、拡張性、長期的な保守コストに直接影響を与える。 > **選択の判断基準** > > Riverpod 3.0は、コンパイル時安全性とボイラープレートの少なさから、大半のプロジェクトに最適である。Bloc 9.0は、金融や医療など監査証跡が必要な規制産業の標準的選択肢となっている。GetXは、移行予算のない既存コードベースの保守にのみ検討すべきである。 ## Riverpod 3.0:コンパイル時安全性と自動リトライ機構 Riverpod 3.0は、Flutterアプリケーションにおける状態の宣言と消費の方法を根本的に変革した。アノテーションベースのコード生成により、依存関係のエラーがランタイムではなくコンパイル時に検出される。これにより、従来は手動テストでしか発見できなかったバグの一群が完全に排除される。 自動リトライ機構は、プロバイダーの計算が失敗した場合にネットワークの一時的なエラーを自動で処理する。設定可能な遅延を用いたリトライにより、エラーリカバリー用のボイラープレートコードが大幅に削減される。 ```dart // counter_provider.dart import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'counter_provider.g.dart'; // Code generation ensures compile-time safety @riverpod class Counter extends _$Counter { @override int build() => 0; // Initial state void increment() => state = state + 1; void decrement() => state = state - 1; void reset() => state = 0; } ``` `@riverpod`アノテーションにより、プロバイダーのボイラープレートがすべて自動生成される。型の不一致、オーバーライドの欠落、循環依存はコンパイル段階で検出される。 ```dart // user_repository_provider.dart import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'user_repository_provider.g.dart'; @riverpod Future currentUser(Ref ref) async { final authService = ref.watch(authServiceProvider); final userId = authService.currentUserId; // Auto-retry on network failure (Riverpod 3.0) final response = await ref.watch( httpClientProvider, ).get('/api/users/$userId'); return User.fromJson(response.data); } ``` Riverpod 3.0では、ウィジェットが画面外に移動した際にプロバイダーのリスナーが自動的に一時停止する。これにより不要な計算が減少し、モバイルデバイスのバッテリー消費が改善される。 ## Bloc 9.0:エンタープライズ向けイベント駆動アーキテクチャ Bloc 9.0は、イベント・状態・ビジネスロジックの厳密な分離を強制する。すべての状態変更が特定のイベントにマッピングされるため、規制産業が要求する監査証跡が自動的に生成される。バージョン9.0で導入されたマウント安全性チェックにより、破棄されたウィジェット上でコールバックが実行されることが防止される。 ```dart // authentication_event.dart sealed class AuthenticationEvent {} final class LoginRequested extends AuthenticationEvent { final String email; final String password; LoginRequested({required this.email, required this.password}); } final class LogoutRequested extends AuthenticationEvent {} final class SessionRestored extends AuthenticationEvent { final String token; SessionRestored({required this.token}); } ``` Dart 3のsealed classにより、イベントの網羅的パターンマッチングが保証される。コンパイラが、すべてのイベント型に対応するハンドラの存在を検証する。 ```dart // authentication_bloc.dart import 'package:flutter_bloc/flutter_bloc.dart'; class AuthenticationBloc extends Bloc { final AuthRepository _authRepo; final TokenStorage _tokenStorage; AuthenticationBloc({ required AuthRepository authRepo, required TokenStorage tokenStorage, }) : _authRepo = authRepo, _tokenStorage = tokenStorage, super(AuthenticationInitial()) { on(_onLoginRequested); on(_onLogoutRequested); on(_onSessionRestored); } Future _onLoginRequested( LoginRequested event, Emitter emit, ) async { emit(AuthenticationLoading()); try { final token = await _authRepo.login( email: event.email, password: event.password, ); await _tokenStorage.save(token); emit(AuthenticationSuccess(token: token)); } catch (e) { emit(AuthenticationFailure(message: e.toString())); } } Future _onLogoutRequested( LogoutRequested event, Emitter emit, ) async { await _tokenStorage.clear(); emit(AuthenticationInitial()); } Future _onSessionRestored( SessionRestored event, Emitter emit, ) async { emit(AuthenticationSuccess(token: event.token)); } } ``` 各イベントハンドラは明確な状態遷移を生成する。ロギングミドルウェアにより、デバッグやコンプライアンス目的ですべてのイベントを記録することが可能である。Bloc 9.0の`EmittableStateStreamableSource`インターフェースは、軽量なモック実装を可能にしテストを簡素化する。 ## Blocイベントトランスフォーマー:高頻度入力の処理 Blocには、一般的な並行処理の問題を解決する組み込みイベントトランスフォーマーが用意されている。インクリメンタルサーチ、連続ボタンタップ、リアルタイムデータストリームの処理が宣言的に実現できる。 ```dart // search_bloc.dart import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class SearchBloc extends Bloc { final SearchRepository _repository; SearchBloc({required SearchRepository repository}) : _repository = repository, super(SearchInitial()) { // restartable() cancels previous search on new input on( _onQueryChanged, transformer: restartable(), ); // droppable() ignores events while processing on( _onResultSelected, transformer: droppable(), ); } Future _onQueryChanged( SearchQueryChanged event, Emitter emit, ) async { if (event.query.length < 3) { emit(SearchInitial()); return; } emit(SearchLoading()); final results = await _repository.search(event.query); emit(SearchLoaded(results: results)); } Future _onResultSelected( SearchResultSelected event, Emitter emit, ) async { emit(SearchNavigating(result: event.result)); } } ``` `restartable()`トランスフォーマーは、新しい入力が到着した時点で進行中の検索をキャンセルし、古い結果が新しい結果を上書きすることを防止する。`droppable()`トランスフォーマーは、ナビゲーション処理中の重複タップを無視する。 ## GetX:技術的負債と移行の現実 GetXは、高速なプロトタイピングと最小限のボイラープレートで人気を博した。しかし2026年現在、ライブラリは保守上の危機に直面している。散発的なアップデート、単一メンテナーへの依存、そして最新Flutter SDKとの互換性の問題が増加している。本番環境のGetXアプリケーションでは、コントローラーのライフサイクル問題や暗黙的グローバルシングルトンによるメモリリークが報告されている。 ```dart // counter_controller.dart (GetX pattern) import 'package:get/get.dart'; // Global singleton - difficult to test and scope class CounterController extends GetxController { final count = 0.obs; // Reactive observable void increment() => count.value++; void decrement() => count.value--; // Lifecycle hooks - disposal timing is unpredictable @override void onClose() { // Cleanup may not execute reliably super.onClose(); } } // Usage in widget class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { // Get.put creates a global singleton final controller = Get.put(CounterController()); return Obx(() => Text('${controller.count}')); } } ``` `Get.put()`はコントローラーをグローバルシングルトンとして登録する。複雑なナビゲーションフローでは、コントローラーが意図したスコープを超えて存続し、メモリを消費する。`.obs`リアクティブ変数はFlutterの標準的な状態通知システムをバイパスするため、他のパッケージとの統合が不安定になる。 ## GetXからRiverpodへの段階的移行手順 GetXコードベースを保守するチームにとって、Riverpodへの移行は段階的に実施可能である。両ライブラリは同一プロジェクト内で共存できるため、全面的な書き換えを行わずに画面単位で変換を進めることができる。 ```dart // Step 1: Replace GetX controller with Riverpod notifier // Before (GetX) class ProductController extends GetxController { final products = [].obs; final isLoading = false.obs; Future loadProducts() async { isLoading.value = true; products.value = await ProductApi.fetchAll(); isLoading.value = false; } } // After (Riverpod 3.0) @riverpod class ProductList extends _$ProductList { @override Future> build() async { // Auto-retry on failure, auto-pause when off-screen return ProductApi.fetchAll(); } Future refresh() async { ref.invalidateSelf(); } } ``` ```dart // Step 2: Replace widget bindings // Before (GetX) class ProductPage extends StatelessWidget { @override Widget build(BuildContext context) { final ctrl = Get.put(ProductController()); return Obx(() { if (ctrl.isLoading.value) return CircularProgressIndicator(); return ListView.builder( itemCount: ctrl.products.length, itemBuilder: (_, i) => ProductTile(ctrl.products[i]), ); }); } } // After (Riverpod 3.0) class ProductPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final productsAsync = ref.watch(productListProvider); return productsAsync.when( loading: () => const CircularProgressIndicator(), error: (err, stack) => ErrorDisplay(error: err), data: (products) => ListView.builder( itemCount: products.length, itemBuilder: (_, i) => ProductTile(products[i]), ), ); } } ``` Riverpod版では、`AsyncValue.when()`を通じてローディング・エラー・データの各状態を明示的に処理する。グローバルシングルトンも手動のライフサイクル管理も不要であり、ウィジェットのアンマウント時に自動的に破棄される。 ## パフォーマンス比較:リビルド効率 リビルド効率はフレームレートに直接影響する。各ソリューションはウィジェットのリビルドを異なる方法で処理し、その差異は数百アイテムを含むリストで顕著に現れる。 | 指標 | Riverpod 3.0 | Bloc 9.0 | GetX | |------|-------------|----------|------| | 選択的リビルド | `select()`フィルター | `BlocSelector` | フィールド毎の`.obs` | | コンパイル時安全性 | 完全(コード生成) | 部分的(sealed class) | なし | | 自動破棄 | 組み込み | `close()`による手動 | 不安定 | | 画面外での一時停止 | 自動(3.0) | 手動 | 非対応 | | イベント追跡 | Provider Observer | 完全なイベントログ | なし | | テスト分離 | `ProviderContainer.test()` | `EmittableStateStreamableSource` | `Get.testMode`が必要 | | バンドルサイズ | 約45KB | 約38KB | 約120KB(ルーティング・DI・HTTP含む) | Riverpodの`select()`メソッドとBlocの`BlocSelector`は、変更されたデータに依存するウィジェットサブツリーのみを更新する外科的なリビルドを実現する。GetXの`.obs`もフィールド単位で同様の粒度を達成するが、依存関係グラフのコンパイル時検証が欠如している。 > **GetXのバンドルサイズに関する注意** > > GetXはルーティング、依存性注入、HTTPクライアント、状態管理を単一パッケージにバンドルしている。状態管理のみを使用するアプリケーションでも、120KB全体のライブラリをインポートすることになる。RiverpodとBlocは単一の責務に特化した軽量パッケージである。 ## 各ソリューションのテスト戦略 テスト容易性は、チーム規模が拡大した際にどのソリューションがスケールするかを左右する重要な要素である。各ライブラリはテストに対するアプローチが異なる。 ```dart // Riverpod test - isolated container import 'package:flutter_test/flutter_test.dart'; import 'package:riverpod/riverpod.dart'; void main() { test('Counter increments', () { final container = ProviderContainer.test(); // Override dependencies for isolation final counter = container.read(counterProvider.notifier); expect(container.read(counterProvider), 0); counter.increment(); expect(container.read(counterProvider), 1); }); } ``` ```dart // Bloc test - event-driven verification import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { blocTest( 'emits [loading, success] on valid login', build: () => AuthenticationBloc( authRepo: MockAuthRepo(), tokenStorage: MockTokenStorage(), ), act: (bloc) => bloc.add( LoginRequested(email: 'dev@test.com', password: 'secure123'), ), expect: () => [ isA(), isA(), ], ); } ``` Riverpodの`ProviderContainer.test()`はテストごとに分離された依存関係グラフを生成する。Blocの`blocTest`ヘルパーは、イベント駆動アーキテクチャに対応した正確な状態遷移シーケンスを検証する。GetXのテストでは`Get.testMode = true`の設定とコントローラーライフサイクルの手動管理が必要であり、CI環境で不安定なテスト結果を招きやすい。 > **面接対策のポイント** > > Flutter状態管理は、モバイル開発者の面接で最も頻繁に問われるトピックの一つである。Riverpod、Bloc、GetXそれぞれのトレードオフを理解し、各ソリューションが適合するケースと適合しないケースを説明できることが、アーキテクチャ的な成熟度を示す。 ## 判断マトリクス:最適なソリューションの選択 プロジェクトの制約条件が最適な選択を決定する。チーム規模、規制要件、既存コードベースのすべてが判断に影響する。 **Riverpod 3.0**は、コンパイル時安全性を重視するチーム、自動エラーリカバリーを伴う非同期データ取得が必要なプロジェクト、またはゼロから構築するコードベースに適している。学習曲線は中程度で、Providerに慣れた開発者は自然に移行できる。 **Bloc 9.0**は、規制産業(フィンテック、ヘルスケア)で運用されるプロジェクト、監査のための完全なイベント追跡が必要なチーム、または決済処理のような複雑な並行ワークフローを扱うアプリケーションに適している。ボイラープレートのコストは、大規模な保守性で回収される。 **GetX**は、移行コストが利用可能な予算を超える既存GetXコードベースの保守にのみ適している。2026年にGetXで新規プロジェクトを開始することは、初日から技術的負債を生み出す。[Flutter公式ドキュメント](https://docs.flutter.dev/data-and-backend/state-mgmt/options)は、推奨ソリューションの一覧にGetXを含めていない。 Flutter状態管理パターンのより深い学習には、[状態管理の基礎モジュール](/technologies/flutter/interview-questions/state-management-basics)が面接で問われる基本概念をカバーしている。[プロバイダーパターンモジュール](/technologies/flutter/interview-questions/provider-pattern)は、3つのソリューションすべてに適用される依存性注入戦略を解説している。 ## まとめ - Riverpod 3.0は、コード生成によるコンパイル時安全性、失敗したプロバイダーの自動リトライ、モバイルデバイスのバッテリー消費を削減する一時停止・再開機能を提供する - Bloc 9.0は、完全な監査機能を備えたイベント駆動型状態遷移を強制し、規制産業におけるエンタープライズアプリケーションの標準となっている - GetXは、散発的なアップデートとSDK互換性の問題により2026年に保守上の危機に直面しており、既存GetXプロジェクトはRiverpodへの段階的移行を計画すべきである - GetXからRiverpodへの移行は、両ライブラリが同一プロジェクト内で共存できるため、画面単位で全面書き換えなしに進められる - テスト分離には大きな違いがある:Riverpodは`ProviderContainer.test()`、Blocは`blocTest`によるイベントシーケンス検証、GetXは脆弱なグローバルテストモードの設定が必要となる - バンドルサイズはモバイルで重要である:Riverpod(約45KB)とBloc(約38KB)は特化型パッケージを提供する一方、GetX(約120KB)は未使用の機能もバンドルする --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ja/blog/flutter/flutter-state-management-2026-riverpod-bloc-getx