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

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

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

# コード

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

```dart: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",
    ];
  },
);
```

```dart: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を実行」と呼ぶことにします。

## 状態の変化結果
状態とは以下のメソッドの`previous`と`next`をコンソール出力したものです。
` 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は初回時のみ返ってくるみたいです。

https://zenn.dev/tsuruo/articles/52f62fc78df6d5

そのことから、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の参照に気をつける必要がありますね。