# 【徹底解説】なぜRiverpod？SetState Providerとの比較

> なぜFlutter開発者はRiverpodを使うのか？Flutterにおける状態管理方法としてRiverpodのメリットやsetState・Providerの課題・問題点を徹底解説します。

- 公開日: 2024-12-16
- 更新日: 2025-01-09
- 著者: Matsu
- タグ: Riverpod
- URL: https://tech.anycloud.co.jp/articles/why-riverpod

---

## はじめに

Riverpod？Provider？setState？

状態管理の議論は尽きないでしょう。

Riverpodはわかりにくい、

setStateで何がダメなの？

Providerで困っていない。

会社のやり方が〇〇だから。

プロジェクトの規模によって変わるよね。

たしかに、その通りなのです。

しかし、だからと言って、新しい技術を知らず、ただ見送って同じものを使い続けるのは良くないはずです。

プロジェクトにおいて、

-   バグが減る
-   工数が削減できる
-   保守性・可読性・パフォーマンスが上がる

これらのメリットがあるならば、既存プロジェクトを書き換える必要はなくても、新規プロジェクトからライブラリを乗り換えても良いのではないでしょうか？

Anycloudでは、Flutterにおける状態管理として、どのプロジェクトでも`setState`と最低限の`Provider`を使用しています。

実際に、プロジェクトを通して大きな問題はありませんが、`view`のコードがカオスになってしまったり、`setState`と`Provider`だけで戦い続けるのはかなりきついと感じています。

ということで今回は、Provider・setStateの課題・問題からRiverpodがおすすめな理由を解説していきます。

## setStateにおける課題・問題

まずは、setStateにおける課題・問題を3つ挙げていきます。

### **1.スコープが限定されている**

`setState`はウィジェット内のローカル状態を更新するのに適していますが、アプリケーション全体の状態を管理するのには適していません。

### **2.過剰なUI再構築**

`setState`が呼ばれると、そのウィジェットとその子ウィジェット全体が再構築されるので、小さな状態変更が不要なUIの更新を引き起こすことがあり、パフォーマンスに悪影響を与えます。

-   レスポンス速度の低下
    -   頻繁に大量のウィジェットを再構築する場合、CPUリソースが大量に消費され、アプリの反応が遅くなります
-   フレームレートの低下
    -   特にアニメーションやスクロールといった動的な操作において、フレームレートの低下を引き起こす可能性があり、いわゆる画面がカクカクと不自然に動く挙動が発生します
-   バッテリー消費の増加
    -   頻繁なUIの再構築はプロセッサ（指示を処理したり、メモリなどを制御したりする装置）を過剰に使用するため、端末のバッテリー消費が増大します

実際の例として良くあるケースが、動的フォームの管理です。

複数の入力フィールドを持つフォームで、`onChange`内の`setState`などで各フィールドの値の変更が全フィールドの再構築を引き起こすような場合、これもまた`setState`の過剰使用が原因でパフォーマンス問題を引き起こします。

フィールドの一つを更新するだけで全フィールドが再描画され、特にフォームが長い場合、タイピングのレスポンスが非常に悪くなります。

### **3.状態の共有が困難**

複数のウィジェット間で状態を共有する場合、その状態を効果的に伝達するためにリフトアップ（親ウィジェットへの状態の移動）やコールバックが必要になりますが、これは複雑でエラーを引き起こしやすいです。

例えば、以下のように、親Widgetにカウンターと2つの子Widgetがあり、これらの子ウィジェットは、それぞれカウンターをインクリメントするボタンを持ちます。

状態は親によって管理され、子ウィジェットは親から状態を受け取って表示します。

※子Widgetでの状態更新は親には伝搬しません

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ParentWidget(),
    );
  }
}

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("State Management Example"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
            ChildWidget(
              incrementCounter: _incrementCounter,
            ),
            ChildWidget(
              incrementCounter: _incrementCounter,
            )
          ],
        ),
      ),
    );
  }
}

class ChildWidget extends StatelessWidget {
  final VoidCallback incrementCounter;

  ChildWidget({required this.incrementCounter});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: incrementCounter,
      child: Text('Increment'),
    );
  }
}

```

## Providerにおける課題・問題

Providerにおける課題・問題を4つ挙げていきます。

### 1.コンテキストへの依存

Providerはコンテキスト（`BuildContext`）に依存しています。

**ライフサイクルの問題**

`BuildContext`が特定のウィジェットに紐づいているため、そのウィジェットがウィジェットツリーから削除された場合には無効になる可能性があります。

これにより、非アクティブなコンテキストを通じてデータにアクセスしようとするとエラーが発生してしまいます。

以下の例では、`UserProfilePage`ウィジェットがユーザーデータを非同期に読み込んでいます。

データの読み込みには2秒かかると設定しており、その間にユーザーがこの画面を離れると、ウィジェットはツリーから削除されます。

非同期処理が完了した後に`setState`を呼び出す際に、そのウィジェットが既にツリーから削除されている場合、`setState`を呼び出すとエラーが発生するので、`setState`を呼び出す前に`mounted`プロパティをチェックして、ウィジェットがまだツリーに存在するかを確認する必要があります。

```dart
import 'package:flutter/material.dart';

class UserProfilePage extends StatefulWidget {
  @override
  _UserProfilePageState createState() => _UserProfilePageState();
}

class _UserProfilePageState extends State<UserProfilePage> {
  String _username = "";

  @override
  void initState() {
    super.initState();
    _loadUserData();
  }

  Future<void> _loadUserData() async {
    try {
      // 擬似的にユーザーデータの読み込みを非同期で模擬
      await Future.delayed(Duration(seconds: 2));
      if (mounted) {
        setState(() {
          _username = "John Doe"; // ユーザーデータが読み込まれたと仮定
        });
      }
    } catch (e) {
      print('Error loading user data: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("User Profile"),
      ),
      body: Center(
        child: Text("Username: $_username"),
      ),
    );
  }
}
```

**非UIロジックの制限**

`BuildContext`はウィジェットツリーに紐づくため、UI以外のロジックでの利用が難しいです。

例えば、ビジネスロジックやバックグラウンドサービスなど、UIコンポーネントから独立して動作する部分で`Provider`を使用する場合、`BuildContext`が必要となります。

ちなみに、`BuildContext`を引数として渡すことが設計上の問題を引き起こす可能性について、さらに詳しく説明しておきます。

`BuildContext`は、Flutterのウィジェットツリーの特定の位置を表すキーであり、ウィジェットが存在する場所に依存して様々なデータやサービスにアクセスするために使用されます。

ウィジェット外でのデータロードやビジネスロジックに`BuildContext`を使用する場合、以下のような問題があります。

-   カプセル化の損失
    -   `BuildContext`をウィジェット外の関数やロジックに渡すことは、そのロジックがUIの詳細（具体的にはウィジェットツリーの構造）に依存することを意味していて、ロジックの再利用性が低下し、テストが難しくなる場合があります。
-   適用範囲の問題
    -   ビジネスロジックやデータ取得のロジックは、通常、UIとは独立して存在するべきですので、`BuildContext`を関数に渡すことは、UIの詳細がビジネスロジックに漏れることを意味しています。
    -   ※MVC、MVVM、またはClean Architectureなどの設計パターンの原則に反することにもなる？

ビジネスロジックやデータアクセス層をUI層から完全に分離することは、アプリケーションの保守性と拡張性を向上させるために重要です。

### 2.不透明なエラーハンドリング

Providerを使用する際、不正な方法でプロバイダーにアクセスした場合（たとえば、プロバイダーが提供されていない場所で呼び出した場合など）、Flutterは実行時エラーを投げますが、Providerでこれはデバッグが難しく（どのWidgetで起きているかなど）、さらにエラーメッセージが直接的でないため、原因の特定が難しくなってしまいます。

### 3.ボイラープレートと複雑性

Providerを使用する際には、プロバイダーのスコープを管理するためのボイラープレートコードが必要です。

※ボイラープレートコード：アプリケーションの異なる部分で繰り返し使用されるコードのことで特に初期設定や設定保持に多くのコードが必要な場合のこと

例えば、各プロバイダーを定義し、適切なスコープで提供するためには、`MultiProvider`や`ChangeNotifierProvider`などが必要です。

各状態やビジネスロジックのために別々のプロバイダーを設定し、それぞれに対応するクラスを定義する必要があり、コードの量が増加し、管理が煩雑になる原因となります。

### 4.グローバルアクセスとテストの容易さ

グローバルアクセスが制限されることがあり、テストが難しくなることがあります。

また、状態のモック化やオーバーライドが複雑になる場合があります。

（※テストコードを触った経験ないのでGPTより）

## Riverpodの具体的なメリットは何か？

setStateやProviderにおける課題・問題は理解できましたか？

ちなみに、Provider vs Riverpod論争の答えは、ドキュメントにも明確に記載されています。

<figure><img src="./image-001.webp" alt="" width="1276" height="508"></figure>

以上を踏まえて、Riverpodによるメリットを解説していきたいと思います。

実は、以前からRiverpodとProviderについての記事を出しており、各記事の中でRiverpodの利点を具体的なコードを交えて解説しています。

<div class="link-card-wrap"><a class="link-card" href="/articles/provider-vs-riverpod-type-problem/" target="_blank" rel="noopener noreferrer"><span class="link-card-body"><span class="link-card-title">【ProviderとRiverpodの違い①】同じ"型"を2つ以上保持できない</span><span class="link-card-description">Flutterにおける状態管理のためのProviderとRiverpodの違いについて、今回の記事では「同じ型のProviderを複数保持できない問題」を解決する方法について解説します。</span><span class="link-card-meta"><img class="link-card-favicon" src="./linkcard-01-favicon.ico" alt=""><span class="link-card-domain">tech.anycloud.co.jp</span></span></span><img class="link-card-image" src="./linkcard-01-image.webp" alt=""></a></div>

<div class="link-card-wrap"><a class="link-card" href="/articles/provider-vs-riverpod-one-value/" target="_blank" rel="noopener noreferrer"><span class="link-card-body"><span class="link-card-title">【ProviderとRiverpodの違い②】一度に 1 つの値しか吐き出さない</span><span class="link-card-description">Flutterにおける状態管理のためのProviderとRiverpodの違いについて、今回の記事では「一度に 1 つの値しか吐き出さない問題」を解決する方法について解説します。</span><span class="link-card-meta"><img class="link-card-favicon" src="./linkcard-02-favicon.ico" alt=""><span class="link-card-domain">tech.anycloud.co.jp</span></span></span><img class="link-card-image" src="./linkcard-02-image.webp" alt=""></a></div>

<div class="link-card-wrap"><a class="link-card" href="/articles/provider-vs-riverpod-difficult-combine/" target="_blank" rel="noopener noreferrer"><span class="link-card-body"><span class="link-card-title">【ProviderとRiverpodの違い③】結合が難しくエラーが発生しやすい</span><span class="link-card-description">Flutterにおける状態管理のためのProviderとRiverpodの違いについて、今回の記事では「結合が難しくエラーが発生しやすい」を解決する方法について解説します。</span><span class="link-card-meta"><img class="link-card-favicon" src="./linkcard-03-favicon.ico" alt=""><span class="link-card-domain">tech.anycloud.co.jp</span></span></span><img class="link-card-image" src="./linkcard-03-image.webp" alt=""></a></div>

<div class="link-card-wrap"><a class="link-card" href="/articles/provider-vs-riverpod-safety/" target="_blank" rel="noopener noreferrer"><span class="link-card-body"><span class="link-card-title">【ProviderとRiverpodの違い④】安全性の欠如</span><span class="link-card-description">Flutterにおける状態管理のためのProviderとRiverpodの違いについて、今回の記事では「安全性の欠如」を解決する方法について解説します。</span><span class="link-card-meta"><img class="link-card-favicon" src="./linkcard-04-favicon.ico" alt=""><span class="link-card-domain">tech.anycloud.co.jp</span></span></span><img class="link-card-image" src="./linkcard-04-image.webp" alt=""></a></div>

<div class="link-card-wrap"><a class="link-card" href="/articles/provider-vs-riverpod-discard-state/" target="_blank" rel="noopener noreferrer"><span class="link-card-body"><span class="link-card-title">【ProviderとRiverpodの違い⑤】状態の破棄が難しい</span><span class="link-card-description">Flutterにおける状態管理のためのProviderとRiverpodの違いについて、今回の記事では「状態の破棄が難しい」を解決する方法について解説します。</span><span class="link-card-meta"><img class="link-card-favicon" src="./linkcard-05-favicon.ico" alt=""><span class="link-card-domain">tech.anycloud.co.jp</span></span></span><img class="link-card-image" src="./linkcard-05-image.webp" alt=""></a></div>

<div class="link-card-wrap"><a class="link-card" href="/articles/provider-vs-riverpod-parameter/" target="_blank" rel="noopener noreferrer"><span class="link-card-body"><span class="link-card-title">【ProviderとRiverpodの違い⑥】信頼性のあるパラメータ化機構の欠如</span><span class="link-card-description">Flutterにおける状態管理のためのProviderとRiverpodの違いについて、今回の記事では「信頼性のあるパラメータ化機構の欠如」を解決する方法について解説します。</span><span class="link-card-meta"><img class="link-card-favicon" src="./linkcard-06-favicon.ico" alt=""><span class="link-card-domain">tech.anycloud.co.jp</span></span></span><img class="link-card-image" src="./linkcard-06-image.webp" alt=""></a></div>

<div class="link-card-wrap"><a class="link-card" href="/articles/provider-vs-riverpod-side-effect/" target="_blank" rel="noopener noreferrer"><span class="link-card-body"><span class="link-card-title">【ProviderとRiverpodの違い⑦】副作用のトリガーが簡単でない</span><span class="link-card-description">Flutterにおける状態管理のためのProviderとRiverpodの違いについて、今回の記事では「副作用のトリガーが簡単でない」を解決する方法について解説します。</span><span class="link-card-meta"><img class="link-card-favicon" src="./linkcard-07-favicon.ico" alt=""><span class="link-card-domain">tech.anycloud.co.jp</span></span></span><img class="link-card-image" src="./linkcard-07-image.webp" alt=""></a></div>

なので、本記事では、詳細な説明は割愛して、何がメリットなのかをリスト形式で簡易的に紹介だけさせてもらいます。

詳細を知りたい方は、是非記事も読んでください。

では改めて、Riverpodのメリットは以下になります。

1.  APIのロジック自体をProvider化することによるAPIを無駄に叩かない、バケツリレーをなくすことができる
2.  AsyncValueのAPIを介して、前のデータ値と新しいロード中の値の 2 つの値を同時に吐き出すので、新しいデータをフェッチしながら既存のデータを保持することができる
3.  `BuildContext`に依存しないため、Widgetのライフサイクルから独立して動作する
4.  グローバルにアクセス可能なものとして扱うことで、Widgetの場所に依存することなく安全にアクセスすることができる
5.  Widgetが破棄された時に、プロバイダーの状態を自動的に破棄することで、不要なリソースの消費を防ぎ、メモリリークのリスクを減らすことができる
6.  外部のパラメータをもとに一意のprovider を宣言できる
7.  Providerを使って何らかの値が変化したときにユーザーに通知するような副作用のトリガーが簡単になる

## Provider から Riverpod への移行は非常に簡単

Riverpodへの乗り換えは簡単に始めることができます。

また、Riverpod と Provider は共存できるので、同時に使用することも可能です。

実際に行う場合、コードベース内の各 Provider インポートに対してエイリアスを使用すればOKです。

<div class="link-card-wrap"><a class="link-card" href="https://riverpod.dev/ja/docs/from_provider/quickstart" target="_blank" rel="noopener noreferrer"><span class="link-card-body"><span class="link-card-title">クイックスタート | Riverpod</span><span class="link-card-description">このセクションは、[Provider]パッケージに慣れている人が Riverpod について学ぶためのものです。</span><span class="link-card-meta"><img class="link-card-favicon" src="./linkcard-08-favicon.svg" alt=""><span class="link-card-domain">riverpod.dev</span></span></span><img class="link-card-image" src="./linkcard-08-image.webp" alt=""></a></div>

## まとめ

Riverpodは柔軟性、拡張性、堅牢な設計により、多くのFlutter開発者から支持されています。

一方で、`setState`、`Provider`、`Bloc`、`Redux`など他の手法もそれぞれに適したシナリオがあり、プロジェクトの要件や開発チームのスキルに応じて最適な選択が異なる場合があります。

しかし、様々なアプリケーションや複雑な状態を持つアプリケーションにおいて、Riverpodは非常に強力なツールとなるため、多くのシナリオでの採用が推奨されているのが現状です。

Riverpodを活用して、より優れたアプリケーション開発ができると良いですね。
