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?

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

Last updated at Posted at 2024-11-02

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

罠のコード

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

/// 未予約のホテル情報
@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つ一気に取得しても良さそうですね)

@riverpod
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();
}

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

まとめ

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

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

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?