# 【ProviderとRiverpodの違い③】結合が難しくエラーが発生しやすい

> Flutterにおける状態管理のためのProviderとRiverpodの違いについて、今回の記事では「結合が難しくエラーが発生しやすい」を解決する方法について解説します。

- 公開日: 2024-10-15
- 更新日: 2024-10-17
- 著者: Matsu
- タグ: Flutter, Riverpod
- URL: https://tech.anycloud.co.jp/articles/provider-vs-riverpod-difficult-combine

---

## はじめに

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

今回の記事では、「[Providerの結合が難しくエラーが発生しやすい](https://riverpod.dev/ja/docs/from_provider/motivation#provider-%E3%81%AE%E7%B5%90%E5%90%88%E3%81%8C%E9%9B%A3%E3%81%97%E3%81%8F%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%8C%E7%99%BA%E7%94%9F%E3%81%97%E3%82%84%E3%81%99%E3%81%84)」というセクションを掘り下げていきます。

## Providerの結合が難しくエラーが発生しやすい

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

> Provider を使用する際、provider の `create`内で `context.watch`を使用したくなるかもしれません。
> 
> これは信頼できず、エラーが発生しやすい方法です。
> 
> `didChangeDependencies` がトリガーされる可能性があります（例：ウィジェットツリーに GlobalKey が含まれている場合など）。
> 
> Provider には `ProxyProvider` という特別なソリューションがありますが、これは面倒でエラーが発生しやすいとされています。
> 
> Riverpod では、[ref.watch](https://riverpod.dev/ja/docs/concepts/reading#using-refwatch-to-observe-a-provider)や[ref.listen](https://riverpod.dev/ja/docs/concepts/reading#using-reflisten-to-react-to-a-provider-change)などのシンプルで強力なユーティリティを使用して、オーバーヘッドなしで値をリアクティブに結合およびキャッシュできます。

```dart
@riverpod
int number(NumberRef ref) {
  return Random().nextInt(10);
}

@riverpod
int doubled(DoubledRef ref) {
  final number = ref.watch(numberProvider);

  return number * 2;
}
```

> 値の結合は Riverpod では自然に感じられます：依存関係は読み取り可能で、API は一貫しています。

### create内でのcontext.watchの使用

Providerの`create`メソッドは、プロバイダーが生成される際に実行される関数というのはご存知のはず。

このメソッド内でデータを初期化し、プロバイダーが提供するデータのインスタンスを作成します。

例えば、`Provider(create: (_) => MyModel())`のように記述しますね。

ここで、`create`メソッド内で他のプロバイダーのデータにアクセスしたくなる場合があります。

その場合、`context.watch()`を使用することですが、これはProviderの`create`コンテキスト内では推奨されていません。

```dart
Provider(
  create: (context) {
    // これはアンチパターン
    var otherData = context.watch<OtherProvider>().data;
    return MyModel(otherData);
  },
)
```

### なぜ推奨されないのか？

`create`メソッド内で`context.watch()`を使用すると、以下の問題が発生する可能性があります。

1.  **ライフサイクルの問題**
    -   `create`メソッドはプロバイダーが作成されるときに一度だけ呼び出されます。このタイミングで他のプロバイダーを`watch`すると、依存関係が正しく設定されず、更新が正確に反映されない可能性があります。
2.  **エラーの発生**
    -   Providerのドキュメントによると、`create`内で`context`を使用することは安全ではなく、特に`watch`を使うと予期しないエラーが発生しやすくなります。これは`context`が完全に構築される前に依存関係を解決しようとするためです。

これを解決する推奨されるアプローチとして、依存関係がある場合は`ProxyProvider`の使用が推奨されていて、他のプロバイダーからのデータを安全に取得し、新しいプロバイダーの生成に利用できます。

しかし、この方法においても `ProxyProvider` は面倒でエラーが発生しやすいとされています。（なぜかまでの詳細はここでの解説は割愛させていただきます。）

## Riverpodにおける結合の改善

Riverpodでは、Providerの依存関係は`ProviderReference`（`ref`）を通じて管理されます。

これにより、`BuildContext`に依存しないため、ウィジェットのライフサイクルから独立して動作し、より信頼性と効率が向上します。

以下の例では、`doubledProvider`は`numberProvider`の値を監視し、その値を2倍にしています。この依存関係は、ウィジェットのライフサイクルに関係なく管理されるため、`BuildContext`の問題を避けることができます。

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

final numberProvider = Provider<int>((ref) => 10);
final doubledProvider = Provider<int>((ref) {
  final number = ref.watch(numberProvider);
  return number * 2;
});

void main() {
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final doubled = ref.watch(doubledProvider);
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("Riverpod Dependency Example")),
        body: Text('Doubled number: $doubled'),
      ),
    );
  }
}
```

## まとめ

Providerで管理していく上での結合における問題点がRiverpodで改善されたことにより、状態管理がさらに扱いやすくなったことが分かりました。

次回、「安全性の欠如」デュエルスタンバイ！

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