メインコンテンツに進む

FAQ

ここではコミュニティからよく寄せられる質問を紹介します:

ref.refreshref.invalidateの違いは何ですか?

ref には provider を再計算するための 2 つのメソッドがあることに気付いたかもしれませんが、それらの違いについて疑問に思うかもしれません。

実はそれほど複雑ではありません。ref.refreshinvalidateread を組み合わせたシンタックスシュガーに過ぎません

T refresh<T>(provider) {
invalidate(provider);
return read(provider);
}

再計算された後の provider の新しい値が気にならない場合は、invalidateを使用します。 新しい値が必要な場合は、代わりにrefreshを使用します。

備考

このロジックはリントルールを通して自動的に適用されます。
返された値を使わずにref.refreshを使うと警告が表示されます。

動作の主な違いは、invalidate した直後に provider を読み込むことで、provider が即座に再計算されることです。
一方、invalidateしても直後に読み込まなければ、更新は後でトリガーされます。

"後で"の更新は一般的には次のフレームの開始時に行われます。 しかし、現在リッスンされていない provider が invalidate された場合、再度リッスンされるまで更新されません。

Ref と WidgetRef の間に共通のインターフェースがないのはなぜですか?

Riverpod は意図的に RefWidgetRefを分離しています。
これはどちらかに依存するコードを書くことを避けるためです。

一つの問題は、RefWidgetRefが似ているように見えるが微妙な違いがあるということです。
両方に依存するコードは、見つけることが難しい点で信頼性に欠けます。

同時に、WidgetRefに依存することはBuildContextに依存することと同じです。
これは事実上、UI レイヤーにロジックを配置することと同じであり、推奨されません。


このようなコードは常にRefを使用するようにリファクタリングすべきです。

この問題の解決策は、ロジックをNotifierに移動することです(詳細は副作用の実行を参照)。
そして、そのロジックをその Notifier のメソッドにします。

これにより、ウィジェットがこのロジックを呼び出したい場合、以下のように書けます:

ref.read(yourNotifierProvider.notifier).yourMethod();

yourMethodは他の provider とのやりとりを行うために、NotifierRefを使用します。

なぜ StatelessWidget を使う代わりに ConsumerWidget を拡張する必要があるのですか?

これは、InheritedWidgetの API の制限によるものです。

いくつかの問題があります:

  • InheritedWidgetの"on change"リスナーを実装することはできません。
    つまり、ref.listenなどをBuildContextと一緒に使用することはできません。

    State.didChangeDependenciesが最も近いものですが、信頼性がありません。
    問題は、ウィジェットツリーが GlobalKey を使用する場合((内部的に一部の Flutter ウィジェットが既にそうしています)、
    依存関係が変更されなくてもライフサイクルがトリガーされる可能性があります。

  • InheritedWidgetをリッスンしているウィジェットはリッスンを停止しません。
    これは、"テーマ"や"メディアクエリ"などの純粋なメタデータには通常問題ありません。

    ビジネスロジックの場合、これは問題です。
    paginated API を表現するために provider を使用するとします。
    ページオフセットが変更されたときに、以前に表示されていたページにウィジェットがリッスンし続けるのを望まないでしょう。

  • InheritedWidgetには、ウィジェットがいつリッスンを停止するかを追跡する方法がありません。
    Riverpod は provider がリッスンされているかどうかを追跡するために時々依存することがあります。

この機能は、自動破棄メカニズムやプロバイダに引数を渡す機能の両方にとって重要です。
これらの機能が Riverpod を非常に強力にしています。

将来的には、これらの問題が解決されるかもしれません。
その場合、Riverpod は Refの代わりに BuildContextを使用するように移行するでしょう。
これにより、ConsumerWidget の代わりに StatelessWidget を使用できるようになります。
しかし、それは次の機会になります!

なぜ hooks_riverpod は flutter_hooks をエクスポートしないのですか?

これは、適切なバージョン管理の慣行を尊重するためです。

flutter_hooksなしでhooks_riverpodを使用することはできませんが、 両方のパッケージは独立してバージョン管理されています。
一方で重大な変更が発生することがありますが、他方では発生しないことがあります。

Riverpod は一部のケースでフィルタの更新に==ではなくidenticalを使用するのはなぜですか?

Notifiers はフィルタの更新に==ではなくidenticalを使用します。

これは、Riverpod ユーザーが Freezed/built_value などのコードジェネレータを使用して copyWith の実装を行うことが多いためです。
これらのパッケージはオブジェクトを比較するために==をオーバーライドします。
オブジェクトの比較は非常にコストがかかります。
"Business logic"モデルには多くのプロパティが含まれる傾向があります。
さらに悪いことに、リスト、マップなどのコレクションもあります。

同時に、複雑な"ビジネス"オブジェクトを利用する時、ほとんどのstate = newState呼び出しは常に通知を発生させます。
(そうでなければ setter を呼び出す必要はありません)
一般的に、現在の状態と新しい状態が等しい場合に state = newState を呼び出すのは、プリミティブオブジェクト(int、enum、string など)に対してです。
これらのオブジェクトは"デフォルトで正規化されています"。このようなオブジェクトが等しい場合、通常は"同一(identical)"であることも意味します。

したがって、Riverpod がフィルタの更新にidenticalを使用するのは、両方の世界にとって良いデフォルトを持つ試みです。
オブジェクトのフィルタリングにこだわらず、コードジェネレータがデフォルトで==オーバーライドを生成するため、==がコストがかかる場合、identicalを使用することで効率的なリスナ-通知方法を提供します。 同時に、単純なオブジェクトの場合、identicalは冗長な通知を正しくフィルタリングします。

最後に、Notifiers のupdateShouldNotifyメソッドをオーバーライドして動作を変更することもできます。

全ての provider を一度にリセットする方法はありますか?

いいえ、全ての provider を一度にリセットする方法はありません。

これは意図的に行われており、アンチパターンと見なされます。
すべての provider を一度にリセットすると、リセットする意図のない provider もリセットされることがよくあります。

これは一般的にユーザがログアウトする時にアプリケーションの状態をリセットしたい場合に尋ねられます。
この機能を望む場合、代わりにユーザの状態に依存するすべてのものをref.watchで"user" provider に設定する必要があります。

その場合、ユーザーがログアウトすると、これに依存するすべての provider が自動的にリセットされますが、その他のものはそのままになります。

"Cannot use "ref" after the widget was disposed"というエラーが発生しました。何が問題ですか?

おそらく"Bad state: No ProviderScope found"も見たことがあるかもしれませんが、これは同じ問題の古いエラーメッセージです。

このエラーは、ウィジェットがマウントされていない状態で ref を使用しようとしたときに発生します。
これは一般的にawaitの後に発生します:

ElevatedButton(
onPressed: () async {
await future;
ref.read(...); // ここで「Cannot use "ref" after the widget was disposed」というエラーが発生する可能性があります
}
)

解決策は、BuildContextと同様に、refを使用する前にmountedを確認することです:

ElevatedButton(
onPressed: () async {
await future;
if (!context.mounted) return;
ref.read(...); // これでエラーは発生しません
}
)