0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Flutter】FutureProviderの再読み込みについて

Posted at

はじめに

サーバーと通信を行うアプリを想定して、エラーハンドリングを含めるとどのような形で実装するのが良さそうかサンプルコードを書こうとしています。
その過程で、FutureProviderの動きについて疑問があったので調査してみました。
調査の内容ですが、再読み込み時にどのような内容に変化するのかということに焦点を当てています。
FutureProviderが再読み込みされるタイミングは下記の二つです(それ以外にもあったら教えてください)
・ref.invalidate実行時
・FutureProvider内でwatchしているプロパイダーが変更された時

この記事では上記の二つの動作でどのように状態が遷移するのかを確認しました。

コード

動作させるコードを以下に書いておきます。

provider.dart
final notificationsErrorProvider = StateProvider((ref) => false);

final notificationsProvider = FutureProvider<List<String>>(
  (ref) async {
    await Future.delayed(
      const Duration(
        seconds: 1,
      ),
    );

    // notificationsErrorProviderをwatchしているので、内容が変わったら再度実行されます。
    if (ref.watch(notificationsErrorProvider)) {
      // throw Exception("notifications error");
      print("error");
    }

    return [
      "notification1",
      "notification2",
    ];
  },
);
page.dart
class ErrorHandlingPage extends ConsumerWidget {
  const ErrorHandlingPage({super.key});
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    ref.listen(notificationsProvider, (previous, next) {
      print("previous isLoading: ${previous?.isLoading}");
      print("previous isRefreshing: ${previous?.isRefreshing}");
      print("previous isReloading: ${previous?.isReloading}");
      print("next isLoading: ${next.isLoading}");
      print("next isRefreshing: ${next.isRefreshing}");
      print("next isReloading: ${next.isReloading}");
      print("previous: $previous");
      print("next: $next");
    });

    final notifications = ref.watch(notificationsProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text('error handling page'),
      ),
      body: SafeArea(
        child: Center(
          child: notifications.isLoading
              ? const CircularProgressIndicator()
              : Column(
                  children: [
                    ElevatedButton(
                      onPressed: () {
                        // ここで確認したい動作のコメントを外し、1か2を実行。
                        // 1:ref.read(notificationsErrorProvider.notifier).state =
                        //     true;
                        // 2:ref.invalidate(notificationsProvider);
                      },
                      child: const Text("change"),
                    ),
                    Expanded(
                      child: ListView.builder(
                        itemCount: notifications.value!.length,
                        itemBuilder: (context, index) {
                          return Text(notifications.value![index]);
                        },
                      ),
                    )
                  ],
                ),
        ),
      ),
    );
  }
}

確認結果

page.dart内の1の動作を「watchしているproviderを変更」、2の動作を「ref.invalidateを実行」と呼ぶことにします。

状態の変化結果

状態とは以下のメソッドのpreviousnextをコンソール出力したものです。
ref.listen(notificationsProvider, (previous, next) {})

watchしているproviderを変更

  • 初回表示時
    以下のようにAsyncLoadingからAsyncDataに変更
    AsyncLoading<List<String>>()AsyncData<List<String>>(value: [notification1, notification2])

  • changeボタン押下時
    以下のようにAsyncDataからAsyncLoadingに変更
    (ローディング状態となる。valueは保持したまま。)
    AsyncData<List<String>>(value: [notification1, notification2])AsyncLoading<List<String>>(value: [notification1, notification2])

  • FutureProvider完了後
    以下のようにAsyncLoadingからAsyncDataに変更
    AsyncLoading<List<String>>(value: [notification1, notification2]) → 
    AsyncData<List<String>>(value: [notification1, notification2])

ref.invalidateを実行

  • 初回表示時
    以下のようにAsyncLoadingからAsyncDataに変更
    AsyncLoading<List<String>>()AsyncData<List<String>>(value: [notification1, notification2])

  • changeボタン押下時
    以下のようにAsyncDataからAsyncData(isLoading: true)に変更
    AsyncData<List<String>>(value: [notification1, notification2])AsyncData<List<String>>(isLoading: true, value: [notification1, notification2])

  • FutureProvider完了後
    以下のようにAsyncData(isLoading: true)からAsyncDataに変更
    AsyncData<List<String>>(isLoading: true, value: [notification1, notification2])AsyncData<List<String>>(value: [notification1, notification2])

違いについてまとめ

違いについてまとめると、以下の点がそれぞれ異なりました。

  • watchしているproviderを変更した場合、AsyncDataがAsyncLoadingとなる
  • ref.invalidateを実行した場合、AsyncDataでisLoadingがtrueとなる

以下の記事によるとAsyncLoadingは初回時のみ返ってくるみたいです。

そのことから、watchしているproviderを変更した場合は初期化されているともいえるのかな?
ただ、そう呼ぶと、ref.invalidateを実行した場合はなんと呼べばいいのかなんとも言えないです。

isLoading,isRefreshing,isReloadingの変化結果

watchしているproviderを変更

  • 初回表示時
プロパティ名 previous next
isLoading true false
isRefreshing false false
isReloading false false
  • changeボタン押下時
プロパティ名 previous next
isLoading false true
isRefreshing false false
isReloading false true
  • FutureProvider完了後
プロパティ名 previous next
isLoading true false
isRefreshing false false
isReloading true false

ref.invalidateを実行

  • 初回表示時
プロパティ名 previous next
isLoading true false
isRefreshing false false
isReloading false false

flutter: previous isLoading: false
flutter: previous isRefreshing: false
flutter: previous isReloading: false
flutter: next isLoading: true
flutter: next isRefreshing: true
flutter: next isReloading: false

  • changeボタン押下時
プロパティ名 previous next
isLoading false true
isRefreshing false true
isReloading false false
  • FutureProvider完了後
プロパティ名 previous next
isLoading true false
isRefreshing true false
isReloading false false

違いについてまとめ

違いについてまとめると、isRefreshingとisReloadingがそれぞれ異なりました。

  • watchしているproviderを変更した場合、isReloadingがtrueとなる
  • ref.invalidateを実行した場合、isRefreshingがtrueとなる

つまり、watchしているproviderを変更した場合はリロード、ref.invalidateを実行した場合はリフレッシュ扱いとなるようです。

さいごに

細かく挙動の違いを見てきましたが、ローディング状態を取得したいときは素直にisLoadingを使えば良さそうです。
watchしているproviderを変更された場合とref.invalidateを実行した場合で挙動を変えたい場合は、isRefreshingとisReloadingの参照に気をつける必要がありますね。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?