🪤

【Riverpod】FutureProviderを扱う際の`asData?.value`の罠

2024/11/02に公開1

こんにちは!okeです!
つい昨日、Riverpodの罠(?)に引っかかっているコードを発見したので、共有します🚀

罠のコード

早速ですが昨日、下記のようなコードを見つけました。
なにが問題かパッと分かるでしょうか?

/// 未予約のホテル情報

Future<List<Hotel>> unbookedHotels(UnbookedHotelsRef ref) async {
  // ホテル情報を取得
  final availableHotels = ref.watch(availableHotelRepositoryProvider).asData?.value ?? [];

  // 予約済みのホテル情報を取得
  final reservedHotels = ref.watch(reservedHotelRepositoryProvider).asData?.value ?? [];

  // 未予約のホテル情報を取得
  return availableHotels.where((hotel) => !reservedHotels.contains(hotel)).toList();
}

ごく自然なコードで特にエラーは出ません。

ただ、このコードは意図しない結果を招く可能性があります。

今回の場合、下記のフローで取得しないと正しい結果が得られません。

  1. 「ホテル情報」を取得・「予約済みのホテル情報」を取得
  2. 「ホテル情報」と「予約済みのホテル情報」から「未予約のホテル情報」を取得

しかし、上記のコードは順番がそのときの状況によって変化してしまい、不整合な結果が出力される可能性があります。

原因

RiverpodにてFutureProviderを扱う際、
ref.watch(provider).whenでdata/error/loadingの値を別々にハンドリングしたりしますが、
ref.watch(provider).asData?.valueでerror/loading時はnullにしたりもしますよね。

今回は.asData?.valueを使用しているのが問題となっています。

具体的には、下記のような流れで問題が出てきます。

  • 初期状態でavailableHotelRepositoryProviderreservedHotelRepositoryProviderはローディング状態で空の配列が渡される。
  • availableHotelRepositoryProviderが先に返ってきた場合に、予約済みのホテル情報は空なので「すべて未予約」としてユーザーに表示されてしまう!

上記の後にreservedHotelRepositoryProviderが更新されて適切なデータに切り替わるかもしれませんが、下記のような問題は出てくるかなと思います。

  • 一瞬画面がチラつく(この指摘を受けて今回調査をしました)
  • どちらかエラー時はずっと正しくない結果が表示される

対策

asData?.valueが有効な場合もあると思いますが、
ちゃんと順を追わないと結果が取得できないフローに関してはawait ref.watch(provider.future)で結果を待ってあげることが重要かと思います。
(今回の場合、Future.waitで2つ一気に取得しても良さそうですね)


Future<List<Hotel>> unbookedHotels(UnbookedHotelsRef ref) async {
  // ホテル情報を取得(処理が終わるまでawaitで待機)
  final availableHotels = await ref.watch(availableHotelRepositoryProvider.future);

  // 予約済みホテル情報を取得(処理が終わるまでawaitで待機)
  final reservedHotels = await ref.watch(reservedHotelRepositoryProvider.future);

  // 未予約のホテル情報を取得(変更なし)
  return availableHotels.where((hotel) => !reservedHotels.contains(hotel)).toList();
}

このようにすることで、データの取得が完了するまで処理を待機し、
エラーが発生した場合には適切にハンドリングできるようになります。

まとめ

なるべくこの罠(?)に引っかかる人が少なくなれば良いなと思い、記事を書きました。

もし「ここが間違っているかも」や「もっと良いやり方あるよ」などあればコメントをください🙇‍♂️

Discussion