キャッシュのクリアとstate破棄への反応
これまでに、state を作成、または更新する方法を見てきました。
しかし、state が破棄されるタイミングについてはまだ話していません。
Riverpod は state 破棄(disposal)と相互作用する様々な方法を提供します。
これは、状態の破棄を遅らせることから破棄に反応することまで多岐にわたります。
状態はいつ破棄されるのか、そしてどう変更するのか?
コード生成を使うことで、デフォルトでは provider がリッスンを停止すると state が破棄されます。
これは、リスナーがフレーム全体に対してアクティブなリスナーがないときに発生します。
この動作はkeepAlive: true
を使うことで回避できます。
全てのリスナーが削除されたときに state が破棄されることを防ぎます。
// stateが自動破棄されることを無効にするため、アノテーションに"keepAlive: true"を指定します。
(keepAlive: true)
int example(Ref ref) {
return 0;
}
自動破棄を有効/無効にしても、provider が再計算されたときに状態が破棄されるかどうかには影響しません。
state は常に provider が再計算されるときに破棄されます。
provider がパラメータを受け取る場合、自動破棄を有効にすることを推奨します。
そうしないと、パラメータの組み合わせごとに state が作成され、メモリリークを引き起こす可能性があります。
state 破棄への反応
Riverpod には、state が破棄される方法がいくつかあります:
- provider が使用されなくなり、"auto dispose"モードになっている場合(詳細は後述)。
この場合、provider と関連するすべての state が破棄されます。 - provider が再計算される場合(
ref.watch
など)。
この場合、以前の state が破棄され、新しい state が作成されます。
どちらの場合も、state が破棄されたときいくつかのロジックを実行したいかもしれません。
これはref.onDispose
を使用することで実現できます。
このメソッドを使用すると、state が破棄されるたびにリスナーを登録できます。
例えば、アクティブなStreamController
を閉じるためにこのメソッドを使用できます:
Stream<int> example(Ref ref) {
final controller = StreamController<int>();
// stateが破棄されると、streamControllerを閉じます。
ref.onDispose(controller.close);
// TO-DO: StreamControllerに値をプッシュする。
return controller.stream;
}
ref.onDispose
のコールバックは副作用を引き起こしてはなりません。
onDispose
内で provider を変更すると予期しない動作を引き起こす可能性があります。
他にも便利なライフサイクルイベントがあります:
ref.onCancel
は、provider の最後のリスナーが削除されたときに呼び出されます。ref.onResume
は、onCancel
が呼び出された後に新しいリスナーが追加されたときに呼び出されます。
ref.onDispose
は何回でも呼び出すことができます。
provider 内の各破棄可能なオブジェクトごとに 1 回ずつ呼び出すことができます。
この方法だと、何かを破棄し忘れた場合に簡単に見つけることができます。
ref.invalidate
を使うことで provider の破棄を強制する
時々、provider を強制的に破棄したい場合があります。
他の provider やウィジェットからref.invalidate
を使用して実行できます。
ref.invalidate
を使用すると、現在の provider の state が破壊されます。
次の二つの結果が考えられます:
- provider がリッスンされている場合、新しい state が作成されます。
- provider がリッスンされていない場合、provider が完全に破棄されます。
class MyWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
return ElevatedButton(
onPressed: () {
// タップすると、providerを破棄します。
ref.invalidate(someProvider);
},
child: const Text('dispose a provider'),
);
}
}
ref.invalidateSelf
を使うことで provider が自身を破棄することができます。
ただし、この場合、常に新しい state が作成されます。
パラメータを受け取る provider を無効にしようとする場合、
特有のペラメータの組み合わせの provider を無効にするか、
全てのペラメータの組み合わせを一度に無効にすることができます:
String label(Ref ref, String userName) {
return 'Hello $userName';
}
// ...
void onTap() {
// このproviderの全ての組み合わせ可能なパラメータを無効にします。
ref.invalidate(labelProvider);
// 指定した組み合わせのみを無効にします。
ref.invalidate(labelProvider('John'));
}
ref.keepAlive
を使用した細かい破棄制御
前述の通り、自動破棄が有効な場合、provider がフルフレームの間リスナーを持たないと state が破棄されます。
しかし、この動作をより細かく制御したい場合があります。
例えば、成功したネットワークリクエストの state を保持し、
失敗したリクエストをキャッシュしないようにしたい場合です。
これは、自動破棄を有効にした後でref.keepAlive
を使用することで実現できます。
これを使用すると、状態が自動的に破棄されるタイミングを決定できます。
Future<String> example(Ref ref) async {
final response = await http.get(Uri.parse('https://example.com'));
// リクエストが成功した後のみ、providerを生かしておく。
// リクエストが失敗した(例外を投げた)場合、providerがリッスンされなくなると、stateは破棄される。
ref.keepAlive();
// `link`を使うことで、自動破棄の動作を元に戻すことができる:
// link.close();
return response.body;
}
provider が再計算されると、自動破棄が再度有効になります。
ref.keepAlive
の戻り値を使用して、自動破棄に戻すことも可能です。
例: 特定の時間だけ state を保持する
現在、Riverpod は特定の時間だけ state を保持するための仕組みを提供していません。
しかし、これまでに見てきたツールを使用して、そのような機能を簡単に実装して再利用可能にすることができます。
Timer
+ ref.keepAlive
を使用して、特定の時間だけ状態を保持できます。
このロジックを再利用可能にするために、拡張メソッド(extension method)で実装することができます:
extension CacheForExtension on Ref {
/// [duration] の間 providerを維持する。
void cacheFor(Duration duration) {
// stateが破壊されるのを防ぐ。
final link = keepAlive();
// 期間経過後、自動破棄を再度有効にする。
final timer = Timer(duration, link.close);
// オプション:providerが再計算される場合(ref.watch など)、
// 保留中のタイマーをキャンセルする.
onDispose(timer.cancel);
}
}
次に、以下のように使用できます:
Future<Object> example(Ref ref) async {
/// 5分間、stateを維持する
ref.cacheFor(const Duration(minutes: 5));
return http.get(Uri.https('example.com'));
}
このロジックは、ニーズに合わせて調整することができます。
例えば、ref.onCancel
/ref.onResume
を使用して、provider が特定の時間聞かれていなかった場合にのみ状態を破棄するようにすることができます。