# 【ProviderとRiverpodの違い⑤】状態の破棄が難しい

> Flutterにおける状態管理のためのProviderとRiverpodの違いについて、今回の記事では「状態の破棄が難しい」を解決する方法について解説します。

- 公開日: 2024-10-18
- 更新日: 2024-10-24
- 著者: Matsu
- タグ: Flutter, Riverpod
- URL: https://tech.anycloud.co.jp/articles/provider-vs-riverpod-discard-state

---

## はじめに

<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-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>

前回に引き続き、今回もRiverpodのモチベーションについて解説していきます。

<div class="link-card-wrap"><a class="link-card" href="https://riverpod.dev/ja/docs/from_provider/motivation" target="_blank" rel="noopener noreferrer"><span class="link-card-body"><span class="link-card-title">モチベーション | Riverpod</span><span class="link-card-description">この記事は、なぜ Riverpod が存在するのかを詳しく説明することを目的としています。</span><span class="link-card-meta"><img class="link-card-favicon" src="./linkcard-02-favicon.svg" alt=""><span class="link-card-domain">riverpod.dev</span></span></span><img class="link-card-image" src="./linkcard-02-image.webp" alt=""></a></div>

今回の記事では、「[状態の破棄が難しい](https://riverpod.dev/ja/docs/from_provider/motivation#%E7%8A%B6%E6%85%8B%E3%81%AE%E7%A0%B4%E6%A3%84%E3%81%8C%E9%9B%A3%E3%81%97%E3%81%84)」というセクションを掘り下げていきます。

## Providerは状態の破棄が難しい

### 公式ドキュメントの説明

> `InheritedWidget` は、consumer が listen を停止したときに反応できません。 このため、Provider は provider の状態を自動的に破棄することができません。
> 
> Provider では、状態が使用されなくなったときにそれを破棄するために provider をスコープする必要がありますが、 これはページ間で状態を共有する場合に特に難しくなります。
> 
> Riverpod は、[autodispose](https://riverpod.dev/ja/docs/concepts/modifiers/auto_dispose)や[keepAlive](https://riverpod.dev/ja/docs/concepts/modifiers/auto_dispose#refkeepalive)などの理解しやすい API を提供し、この問題を解決します。
> 
> これらの API は、時間ベースのキャッシングなど、柔軟でクリエイティブなキャッシング戦略を可能にします:

```dart

// コード生成を使用すると、.autoDisposeがデフォルトになります。
@riverpod
int diceRoll(DiceRollRef ref) {
  // このproviderは.autoDisposeであるため、,
  // 監視を解除すると現在公開されている状態が破棄されます。
  // その後、このproviderが再び監視されると、
  // 新しいdiceが振られ再び公開されます。
  final dice = Random().nextInt(10);
  return dice;
}

@riverpod
int cachedDiceRoll(CachedDiceRollRef ref) {
  final coin = Random().nextInt(10);
  if (coin > 5) throw Exception('Way too large.');
  // 上記の条件が失敗する可能性があります。
  // そうでない場合、以下の指示により、誰もリスニングしていなくても
  // providerにキャッシュされた状態を維持させます。
  ref.keepAlive();
  return coin;
}
```

> 残念ながら、これは生の `InheritedWidget` では実装できないため、Provider でも実現できません。

### Providerにおける状態の破棄の課題

Provider では、`InheritedWidget`に基づいて構築されており、ウィジェットツリーにおいてデータの消費者（consumers）がそのリスニングを停止した際に、プロバイダーがその変化に対応することが困難です。

これは、Providerの状態が、ウィジェットツリーから削除された時に自動的に破棄されないため、不要なメモリ保持を引き起こす可能性があります。

ここでは具体的なコードは出さないですが、特定の状態をページ間で共有しつつ、そのページがポップされた時に適切に状態をクリーンアップしない場合にページが消えた後も状態がメモリに残り続けることがあります。

## Riverpodにおける状態の改善

Riverpod では、 `.autoDispose` 修飾子と `ref.keepAlive()` メソッドを通じて、状態のライフサイクル管理を細かくコントロールすることができます。

### **autoDispose**

<div class="link-card-wrap"><a class="link-card" href="https://riverpod.dev/docs/concepts/modifiers/auto_dispose" target="_blank" rel="noopener noreferrer"><span class="link-card-body"><span class="link-card-title">https://riverpod.dev/docs/concepts/modifiers/auto_dispose</span><span class="link-card-meta"><span class="link-card-domain">riverpod.dev</span></span></span></a></div>

プロバイダーが画面上で必要とされない時（つまり監視しているウィジェットがすべて破棄された時）に、プロバイダーの状態を自動的に破棄します。

これにより、不要なリソースの消費を防ぎ、メモリリークのリスクを減らすことができます。

以下実際の用途としてイメージが湧きやすい参考サイトを貼っておきます。

<div class="link-card-wrap"><a class="link-card" href="https://zenn.dev/riscait/books/flutter-riverpod-practical-introduction/viewer/auto-dispose" target="_blank" rel="noopener noreferrer"><span class="link-card-body"><span class="link-card-title">AutoDispose修飾子で、Providerを自動破棄させる</span><span class="link-card-meta"><img class="link-card-favicon" src="./linkcard-04-favicon.webp" alt=""><span class="link-card-domain">zenn.dev</span></span></span><img class="link-card-image" src="./linkcard-04-image.webp" alt=""></a></div>

### **keepAlive**

プロバイダーの状態をアクティブに保つために使用されます。

これにより、特定の条件下で状態を意図的に保持することが可能になり、例えばバックグラウンドでのデータフェッチや長時間にわたる計算が必要な場合に有効です。

実際の例として以下コードでは、リクエストが失敗し、ユーザーが画面を離れてから再度画面に入ると、リクエストが再度実行されます。

ただし、リクエストが正常に完了した場合、状態は保持され、画面に再度入っても新しいリクエストはトリガーされません。

```dart
final myProvider = FutureProvider.autoDispose((ref) async {
  final response = await httpClient.get(...);
  ref.keepAlive();
  return response;
});
```

## まとめ

状態管理ライブラリがプロバイダーを生成巣るタイミング、アップデートするタイミング、そして必要がなくなったときにどのように破棄されるかを厳格に制御することにより、アプリケーションのパフォーマンスが向上し、メモリリークなどの問題を避けることができます。

次回、「信頼性のあるパラメータ化機構の欠如」デュエルスタンバイ！

<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-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>
