LASSIC Media らしくメディア

2026.07.06 らしくコラム

Flutterの状態管理を外注でリファクタ

LASSIC IT事業部|元請(プライムベンダー)としてシステム保守・運用を受託

Flutter状態管理リファクタのイメージ

この記事のポイント

  • setState中心のFlutterアプリが保守フェーズでどのような課題を抱えやすいかを整理します。
  • Riverpod・BLoC/Cubitなど代表的な状態管理アプローチの考え方の違いを一次情報に基づいて解説します。
  • 既存アプリを段階的にリファクタリングする進め方と、外注・内製を判断する視点をまとめます。

Flutterの状態管理とsetStateの限界

状態管理アーキテクチャのイメージ

Flutterの状態管理とは、画面に表示するデータ(状態)の変化をウィジェットの再描画に反映させる仕組みを指します*1。Flutter公式ドキュメントは、状態管理を単一の正解がないテーマとして扱っており、「一部の疑問に答えが見つからない、あるいは紹介されているアプローチが自社のユースケースに合わないと感じるなら、その感覚はおそらく正しい」と明言しています*1

StatefulWidget(状態を持つウィジェット)とsetStateは、Flutterが標準で提供する最も基本的な状態管理手段です*1。画面内の値を変更してsetStateを呼び出すと、Flutterはそのウィジェットのbuildメソッドを再実行し、UIを更新します。小規模な画面や単一ウィジェットで完結する処理であれば、この仕組みだけで十分に機能します。

課題が表面化するのは、アプリの規模が拡大した局面です。複数の画面から同じデータを参照したり、ビジネスロジックとUI描画のコードが同一クラスに混在したりすると、setStateの呼び出し箇所が増加します。結果として、どの状態変更がどの再描画を引き起こすのかを追跡しづらくなり、改修時の影響範囲の見積もりが難しくなります。

公式ドキュメントも、アプリの複雑さやチームの方針に応じて、状態管理パッケージの採用を検討する価値があると位置づけています*1。次章では、状態を「エフェメラル」と「アプリ」の2種類に分けて考える公式の枠組みを確認します。

図
Flutter状態管理リファクタリングの進め方(現状分析から検証までの5ステップ)

Ephemeral StateとApp Stateの切り分け

Flutter公式ドキュメントは、状態を「Ephemeral State(一時的な状態)」と「App State(アプリ状態)」の2つに分類しています*2。Ephemeral Stateは単一のウィジェット内で完結し、アプリの他の部分と共有したりセッションをまたいで保持したりする必要がない状態です*2

例として、PageViewの現在ページ、アニメーションの進捗、BottomNavigationBarで選択中のタブが挙げられています*2。これらはStatefulWidgetとsetStateで自然に管理できるため、公式も高度な状態管理手法を持ち込む必要はないとしています*2

一方App Stateは、アプリの複数箇所からアクセスされ、ユーザーセッションをまたいで保持する必要がある状態を指します*2。ユーザー設定、ログイン情報、ECアプリのカート内容、ニュースアプリの既読・未読状態などが該当し、こうした状態にはProviderやRiverpodのような状態管理の仕組みが必要になるとされています*2

両者を分ける明確な基準は存在せず、公式ドキュメントもRedux作者Dan Abramov氏の言葉を引用し「より違和感のない方を選ぶ」という経験則を紹介しています*2。リファクタリングの初手は、この分類に沿って画面ごとの状態をEphemeralとAppに仕分ける作業から始めることが有効です。

Provider・ChangeNotifierが担う中間的な選択肢

InheritedWidget(ウィジェットツリーの祖先から子孫へ値を伝える基礎的な仕組み)は、Flutterが提供する低レベルの技術であり、providerをはじめとする多くのコミュニティパッケージの基礎になっています*1。ただし素のInheritedWidgetは記述量が多く、直接使うケースは限られます。

providerパッケージは、このInheritedWidgetをラップし、より使いやすく再利用しやすい形にしたものです*3。公式パッケージ説明では、リソースの割り当てと破棄の簡素化、ボイラープレート(定型コード)の削減、値が必要になるまで初期化を遅らせる遅延ロード、Flutter DevToolsでの状態可視化が特徴として挙げられています*3

providerはChangeNotifier(状態の変更をリスナーに通知する仕組み)と組み合わせて使う設計が推奨されています*3。ChangeNotifierを継承したクラスに状態とロジックをまとめ、notifyListenersを呼ぶことでUIに変更を伝える構成です。setStateを画面から切り出し、複数ウィジェットで共有できる状態として扱える点が、setState単体との違いになります。

providerはFlutter公式の「Simple app state management」ページでも紹介されており*1、小〜中規模のアプリでsetStateからの移行先として検討しやすい選択肢です。ただしコンパイル時の型チェックやテスト時のオーバーライドの容易さという点では、後述するRiverpodがより強い設計を提供しています。

Riverpodが解決するコンパイル時の型検査とテスト容易性

Riverpodとは、Dart・Flutterアプリケーション向けの「リアクティブなキャッシングとデータバインディングのフレームワーク」です*4。providerパッケージの作者が、その課題を踏まえて設計したライブラリという位置づけになります。

Riverpod公式サイトは、解決する課題として、main.dartや複数のUIファイルにビジネスロジックが散在する問題、buildメソッド内でのフィルタリングやソート処理の重複、ローディング・エラー状態を手動管理する非同期処理の複雑さを挙げています*4

主な特徴としてまず挙げられるのが、コンパイル時の型検査です*4。ウィジェットツリーに依存しない仕組みでプロバイダーを宣言するため、BuildContextを介さずに値へアクセスでき、多くのエラーを実行前のコンパイル段階で検出できます。加えてカスタムリントルールも提供され、型の堅牢性をさらに補強します*4

もう一つの特徴がテスト容易性です*4。Riverpodは状態を任意の場所に配置しながらテスト性を維持できる構造を持ち、依存性の注入がしやすい設計になっています*4。ホットリロードへの対応やDevTools統合による状態検査も備えており*4、開発中の可視性を高める効果が期待できます。setStateやProviderからの移行先として、中〜大規模アプリで採用が検討される理由はこのコンパイル時の型検査とテスト容易性にあります。

BLoC・Cubitが分離するプレゼンテーションとビジネスロジック

BLoC(Business Logic Component)は、プレゼンテーション層とビジネスロジック層を分離するためのデザインパターンです*5。flutter_blocパッケージの公式説明では、ウィジェットが「何が起こるかを知らず、ユーザーのアクションを通知するだけ」の状態にすることで、関心の分離を実現するとされています*5

このパッケージはCubitとBlocという2種類のクラスを提供します*5。両者はいずれもBlocBuilder・BlocListener・BlocProviderといった同じウィジェット群と統合できますが*5、Cubitはより簡潔な実装に向き、Blocはより複雑な状態管理に適した設計とされています*5。実装上は、Cubitがメソッド呼び出しを起点に状態を出力するのに対し、Blocはイベントを受け取りそれを状態に変換する形を取ります。

BlocProviderは依存性注入の役割を担い、Cubit・Blocのインスタンスをウィジェットツリーに提供します*5。複数のプロバイダーやリスナーが必要な画面では、MultiBlocProvider・MultiBlocListenerを使うことでネストの深さを抑えられます*5。ビジネスロジックをウィジェットから切り離す設計は、テスト性と保守性の向上につながるとされており*5、チーム開発で役割分担を明確にしたい場合に選ばれやすい傾向があります。

RiverpodとBLoC/Cubitはいずれもロジックの分離を志向する点で共通しますが、状態の宣言方法やイベント駆動の有無に違いがあります。次章では両者を含めた比較を整理します。

状態管理アプローチの比較と選定軸

リファクタ外注のイメージ

ここまで確認したsetState、Provider、Riverpod、BLoC/Cubitは、それぞれ対象とする状態の規模と設計思想が異なります。以下の比較表に、公式情報に基づく特徴を整理しました。

アプローチ 対象とする状態 設計思想の特徴
setState 単一ウィジェット内のEphemeral State*2 Flutter標準の最も基本的な仕組み。
局所的な状態にはこれで十分とされる*1
Provider+ChangeNotifier 複数ウィジェットで共有するApp State*2 InheritedWidgetをラップしボイラープレートを削減*3
ChangeNotifierと組み合わせて使う設計*3
Riverpod アプリ全体のApp State・非同期状態*4 コンパイル時の型検査とテスト容易性を重視*4
BuildContextに依存しない設計*4
BLoC/Cubit プレゼンテーションから分離したビジネスロジック*5 Cubitはメソッド呼び出し起点、Blocはイベント駆動*5
関心の分離とテスト性を重視*5

Flutter公式は、状態管理パッケージの採用可否をアプリの複雑さとチームの好みに応じて判断するべきだとしており*1、単一の正解があるわけではありません。既存アプリの改修であれば、setStateが密集している画面から優先的に洗い出し、共有が必要なApp State部分だけをRiverpodやBLoC/Cubitに寄せていく判断が現実的です。

選定を誤ると手戻りのコストが大きい点にも注意が必要です。ChangeNotifierベースの設計からRiverpodやBLoCへ後から移行する場合、状態の持ち方(クラス構造)自体を書き直すことになり、関連するウィジェット・テストコードまで影響が及びます。移行方針は着手前に固めておくことが望まれます。

既存Flutterアプリを段階的に移行する進め方

稼働中のアプリを一括で書き換えると、リグレッション(既存機能の不具合発生)のリスクが高くなります。そのため、画面単位・機能単位で段階的に移行する進め方が実務では取られやすい方針です。

第一段階は、setStateやChangeNotifierが密集している箇所の棚卸しです。前章のEphemeral StateとApp Stateの分類に沿って、共有が必要な状態を持つ画面から優先度を付けます。この段階で移行対象と対象外の境界線を明確にしておくと、後続作業の見積もりがしやすくなります。

第二段階は、優先度の高い画面から新しい状態管理の仕組みに置き換える作業です。RiverpodやBLoC/Cubitへの移行では、既存のUIウィジェットとロジック層を同時に書き換えるのではなく、まずロジック層(Notifier・Cubit・Bloc)を新設し、UIから呼び出す形に段階的に差し替えると、影響範囲を絞り込みやすくなります。

移行を内製だけで進める場合、必要になるのはFlutterの基礎知識に加え、対象フレームワーク(Riverpod・BLoC等)の設計思想の理解、既存コードの依存関係を読み解く力、回帰試験を設計する力です。これらを兼ね備えた人員を複数名、一定期間確保する必要があり、通常の開発と並行して進めることになる分、体制面の負荷は小さくありません。

外注・内製を分ける判断軸

状態管理のリファクタリングは、機能追加とは異なり、直接的な新規価値を生むわけではありません。そのため社内では優先度が下がりやすく、着手が先送りされやすい作業でもあります。この点を踏まえ、外注と内製のどちらを選ぶかは、体制と期限の両面から検討することが大切です。

内製を選ぶ場合、既存コードへの理解が深いメンバーが担当できる利点がある一方、通常の開発ロードマップと並行してリファクタリング工数を確保する必要があります。片手間で進めると設計判断が揺れやすく、途中でRiverpod用とBLoC用のコードが混在するといった状態に陥りやすい点には注意が必要です。

外注を選ぶ場合は、Flutterの状態管理設計に実務経験を持つパートナーを選ぶことが前提になります*6。依頼前には、対象アプリの規模(画面数・状態を共有する範囲)、移行のゴール(特定フレームワークへの統一か、部分的な整理か)、既存の自動テストの有無を整理しておくと、見積もりの精度が上がります。

専門パートナーに依頼した場合と内製で完結させた場合の違いは、設計判断のスピードと、複数プロジェクトで得た移行パターンの蓄積にあります。内製では初めての移行が手探りになりやすいのに対し、外部の専門パートナーは類似の移行経験を踏まえて設計の妥当性を検証しながら進められる点が違いになります。移行対象の切り分けやテスト設計も含めて相談できる体制を選ぶと、移行途中の手戻りを抑えやすくなります。

まとめ:Flutter状態管理リファクタリングの3つの判断軸

本稿では、setState中心のFlutterアプリが抱える保守性の課題、Provider・Riverpod・BLoC/Cubitという代表的な状態管理アプローチの考え方の違い、そして段階的な移行の進め方を整理しました。要点は3つに集約できます。第一に、状態はEphemeral StateとApp Stateに分類し、共有が必要な部分だけを新しい仕組みに寄せることです*2。第二に、Riverpodはコンパイル時の型検査とテスト容易性を、BLoC/Cubitはプレゼンテーションとロジックの分離を重視するという設計思想の違いを踏まえて選ぶことです*4*5。第三に、移行は画面・機能単位で段階的に進め、体制が確保できない場合は外部の専門パートナーへの相談を選択肢に入れることです。

LASSICに相談するメリット

LASSICは元請としてシステムの保守・運用を受託する体制を整えており、稼働中のアプリに手を入れるリファクタリング案件でも、既存仕様への影響範囲を確認しながら進める進め方に対応します。Flutterの状態管理設計を含む改修のご相談は、現状のコード構成や移行のゴールをヒアリングしたうえで、段階的な移行計画をご提案します。

よくある質問

setStateからRiverpodやBLoCへの移行は一括で行う必要がありますか。

一括で行う必要はありません。Flutter公式もEphemeral StateとApp Stateを分類し、共有が必要な状態だけを状態管理の仕組みに寄せる考え方を示しています*2。画面や機能の単位で優先度を付け、段階的に置き換える進め方が現実的です。

RiverpodとBLoCはどちらを選ぶべきですか。

どちらが優れているという一律の答えはありません。Riverpodはコンパイル時の型検査とテスト容易性を重視した設計です*4。BLoC/Cubitはプレゼンテーション層とビジネスロジック層の分離を重視した設計です*5。既存コードの構成やチームの経験に応じて選ぶことになります。

GetXなど他の状態管理パッケージも検討対象になりますか。

Flutter公式は特定のパッケージを一つに絞らず、pub.devの状態管理トピックから候補を探すことを案内しています*1。GetXを含む他のパッケージも選択肢になりますが、採用にあたってはアプリの複雑さとチームの方針に照らして判断する必要があります*1

状態管理のリファクタリング中にアプリの機能を止めずに進められますか。

画面・機能単位で段階的に移行する進め方であれば、リリースサイクルを止めずに進めることが可能です。ただし移行対象の切り分けと回帰試験の設計を事前に固めておかないと、移行途中でロジックが混在し不具合の温床になりやすい点には注意が必要です。

Flutterの状態管理リファクタリングを外注する場合、何を準備しておくとよいですか。

対象アプリの画面数や状態を共有する範囲、移行のゴール(特定フレームワークへの統一か部分的な整理か)、既存の自動テストの有無を整理しておくと、外注先との認識合わせがスムーズになります*6。既存コードの仕様書が残っていない場合は、その旨も事前に伝えておくとよいでしょう。

著者:テレリモ総研編集部 鈴木 亮佑


ITアウトソーシング・システム開発のご相談はLASSICへ

元請として、貴社の課題に合わせた体制構築・開発支援をご提案します。まずはお気軽にご相談ください。

無料相談はこちら

ご不明な点はお問い合わせフォームからもご連絡いただけます。

  1. *1 出典:Flutter公式ドキュメント「Approaches to state management / State management
  2. *2 出典:Flutter公式ドキュメント「Differentiate between ephemeral state and app state
  3. *3 出典:pub.dev「provider package
  4. *4 出典:Riverpod公式サイト「Riverpod – A Reactive Caching and Data-binding Framework
  5. *5 出典:pub.dev「flutter_bloc package
  6. *6 出典:LASSIC「ITアウトソーシングサービス

View