Chapter 17

[v0.14.0以下版] context.refreshでProviderを更新する

村松龍之介
村松龍之介
2023.02.12に更新

強制的にProviderを更新させる context.refresh

FutureProviderでは、非同期処理で値を取得できます。
例えば、APIからのJSON取得です。

しかし、Streamと違ってFutureの場合は1度取得が終わったら、通常更新はされません。

手動でもう一度非同期処理を実行させて、最新の値を取得したい場合はどうしたら良いのでしょうか?

context.refresh を使うことで実現できます。

refreshメソッド

https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/BuildContextX/refresh.html

RefreshIndicatorを使ってAPIをリロードする例

FutureProviderを使ってGitHubのAPIを実行して結果をMap型で返すProviderを作ります。

import 'dart:convert';
import 'package:http/http.dart' as http;

final githubUserProvider = FutureProvider<Map<String, Object?>>((ref) async {
  final response = await http.get(Uri.https(
    'api.github.com',
    'users/$username',
  ));
  return json.decode(response.body);
});

Widget側でFutureProviderを watch します。
FutureProviderは .future を付けることで Future型として取得することもできますが、
Riverpodを使用しているなら AsyncValue<T>[1]として使った方が素直です。
.when を使うことで loading: 読み込み中 , error: エラー発生 の状態を忘れずに処理することができます。
非同期処理が完了して取得したデータは data で受け取りハンドリングします。


Widget build(BuildContext context, ScopedReader watch) {
  return watch(githubUserProvider).when(
    // 非同期処理実行中はインジケーターを表示しておく
    loading: () => const CircularProgressIndicator(),
    // エラーが発生した場合はエラー情報を表示します。
    // ここでもボタンを表示して `context.refresh` できるようにすると親切ですね。
    error: (error, stack) => Text('Error: $error, $stack'),
    // 非同期処理が完了したら `data` プロパティの引数として受け取れる。
    data: (user) {
      return RefreshIndicator(
        // RefreshIndicatorでListView等を囲み、 `onRefresh` で動作を指定すれば引っ張って更新できるようになる
        // `context.refresh()` でProviderを更新します。
        onRefresh: () => context.refresh(_githubUserProvider),
        child: ListView(
          padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 16),
          children: [
            ListTile(
              title: const Text('login'),
              subtitle: Text('${user['login'] ?? 'none'}'),
            ),
            ListTile(
              title: const Text('id'),
              subtitle: Text('${user['id'] ?? 'none'}'),
            ),
            ListTile(
              title: const Text('html_url'),
              subtitle: Text('${user['html_url'] ?? 'none'}'),
            ),
          ],
        ),
      );
    },
  );
}

AsyncValue<T> 使用時に、 loading:error: のハンドリングが不要な時は、
.data?.value と書くことで、 loading or error 状態の時は null が、非同期処理が完了すれば値が取得できるのでそちらを使いましょう。

// Map<String, Object?>? 型となるので、 利用時にnullチェックが必要です
final user = watch(githubUserProvider).data?.value;
脚注
  1. https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValue-class.html ↩︎