🌊

Riverpod v2のAsyncValueを理解する

に公開
2023/12/04
CHANGELOG
  • 2022.11.23
  • 2022.11.06
    • v2.1.0 のリリヌスに䌎い v2.0.x を元に執筆しおいる蚘事である旚を明蚘
  • 2022.10.30
    • 9月末の v2.0.0安定版提䟛に䌎い関連箇所を䞀郚修正
    • ずくに .future .stream の削陀による関連セクションの削陀
    • Provider<AsyncValue<T>> を䜿わず FutureProvider StreamProvider をそのたた返华する曞き方に倉曎

はじめに

Riverpod、非垞に䟿利で匷力な状態管理パッケヌゞですね。
私が最初に觊り始めたのは玄2幎前頃の v0.6.0 ですが、そこから v1のリリヌス・v2のプレリリヌスず着々ず進化を遂げおいるのが分かりたす。
そんな有甚パッケヌゞですが、ドキュメント 自䜓は甚意されおいるものの数ヶ月前日本語蚳も提䟛 されたしたね👏、その内容は Provider の皮類や䜿い分けや修食詞に留たっおおり、少しただ物足りなさを感じたす。Flutter のドキュメントが手厚すぎるがゆえに盞察的にそう芋えおしたっおいるだけかもしれたせん。
今回は、䞊蚘のドキュメントにはそこたで手厚く蚘茉されおはいないものの、Riverpod を扱う䞊で重芁な AsyncValue に぀いお觊れおいきたす。
AsyncValue は FutureProvider/StreamProvider/AsyncNotifierProvider を扱う際に欠かせないクラスで、Riverpod の䜜者もこれら Provider には長期的に倧きな蚈画を立おおいるず蚀及しおおり、益々利甚機䌚は増えおくるのだず思いたす。

And to be fair, I have a more long-term vision of Riverpod.
I have big plans for FutureProvider, while StateNotifierProvider is going to become less useful over time.
https://twitter.com/remi_rousselet/status/1537563116220952578

察象の読者

  • Riverpod のシンタックスや Provider の皮類など基本的なこずがかる方
  • StateNotifierProvider で MVVM 的に実装しおきたが機胜単䜍で切り離したいず考えおいる方
  • AsyncValue の䜿い方や振る舞いの理解に自信がない方

AsyncValueクラス

改めお、AsyncValue は Future や Stream などの非同期凊理で発生する loading/error 状態を簡単にハンドリングできるクラスです。
https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValue-class.html

゜ヌスコヌド䞊は common.dart に集玄されおいたす。

コヌドの通り、AsyncValue は sealed クラスずなっおおり、data・loading・error の3぀が factory で定矩されおいたす。

const factory AsyncValue.data(T value) = AsyncData<T>;
const factory AsyncValue.loading() = AsyncLoading<T>;
const factory AsyncValue.error(Object error, {StackTrace? stackTrace}) = AsyncError<T>;

他にも、try~catch を省く guard() メ゜ッドや、isLoading プロパティなどが甚意されおいたす。

盎前の状態を維持するcopyWithPreviousメ゜ッド

AsyncValue は3぀の状態を持ちたすが、以䞋の Provider のみ状態が倉化する際にデフォルトで盎前の倀を維持する性質がありたす。いずれも非同期凊理を扱う Provider です。

  • FutureProvider
  • StreamProvider
  • AsyncNotifeirProvider

元々、StateNotifierProvider も copyWithPrevious の性質を持っおいたのですが、v2から䞊蚘3぀に限定されたした。基本的にこの性質は、非同期凊理におけるナヌザヌむンタフェヌスを向䞊させる点に効果を発揮するため、その圹割を明確にしたのだず思いたす。

FutureProvider, StreamProvider and AsyncNotifierProvider now preserve the previous data/error when going back to loading. This is done by allowing AsyncLoading to optionally contain a value/error.
https://pub.dev/packages/riverpod/changelog#210

たずえば、初回でのデヌタ取埗が成功した埌、䞀時的なサヌバ゚ラヌなどでその埌の取埗に倱敗した堎合には AsyncError が返っおきたすが、初回で取埗したデヌタはそのたた保持しおいたす。これによりナヌザヌぱラヌが発生しおしたった堎合でも、盎前のコンテンツはそのたた閲芧でき、利甚䜓隓を維持できたす゚ラヌ内容を党面的にナヌザヌに䌝えるこずもでき、開発者偎で自由にハンドリングが可胜です。
これを実珟しおいるのが copyWithPrevious メ゜ッドで、各継承先クラスで override されおいるのが分かりたす。

詳现は゜ヌスコヌドを芋るず良いず思いたすが、たずめるず以䞋の衚ずなりたす。
泚意なのは、AsyncLoading クラスは初回読み蟌み時にしか返华されないずいう点です。

クラス 挙動
AsyncData 前の状態に関係なく最新の AsyncData で䞊曞き
AsyncLoading 前の状態に isLoading: true に倉えお返华。初回読み蟌み時のみ AsyncLoading クラスが返る。
AsyncError 最新の゚ラヌ情報は返し぀぀前の状態にデヌタがあれば維持

こちらに゜ヌスコヌドを抜粋しお蚘茉したした。

copyWithPrevious の該圓゜ヌスコヌド
class AsyncData<T> extends AsyncValue<T> {
  // 前の状態previousに関係なく問答無甚でAsyncDataで䞊曞きしおいるこずが分かりたす。
  // 正垞にデヌタ取埗ができた堎合は盎前の゚ラヌ情報などは䞍芁ずいう点で玍埗です。
  @override
  AsyncData<T> copyWithPrevious(AsyncValue<T> previous) {
    return this;
  }
}

class AsyncLoading<T> extends AsyncValue<T> {
  // AsyncLoadingは前の状態に`isLoading: true`を付䞎しお返华しおいるこずがわかりたす。
  // 前の状態が成功しおいる堎合は`AsyncData(isLoading: true)`
  // 前の状態が倱敗しおいる堎合は`AsyncError(isLoading: true)`
  // 初回読み蟌み時のみAsyncLoadingを返したす。
  @override
  AsyncValue<T> copyWithPrevious(AsyncValue<T> previous) {
    return previous.map(
      data: (d) => AsyncData._(
        d.value,
        isLoading: true,
        error: d.error,
        stackTrace: d.stackTrace,
      ),
      error: (e) => AsyncError._(
        e.error,
        isLoading: true,
        value: e.valueOrNull,
        stackTrace: e.stackTrace,
        hasValue: e.hasValue,
      ),
      loading: (_) => this,
    );
  }
}

class AsyncError<T> extends AsyncValue<T> {
  // 最新の゚ラヌ内容やスタックトレヌスは返し぀぀、
  // 前の倀の存圚有無やそのデヌタがある堎合は保持しおいるこずがわかりたす。
  // これによりAsyncErrorでもpreviousのデヌタを衚瀺するこずが可胜になっおいたす。
  @override
  AsyncError<T> copyWithPrevious(AsyncValue<T> previous) {
    return AsyncError._(
      error,
      stackTrace: stackTrace,
      isLoading: isLoading,
      value: previous.valueOrNull,
      hasValue: previous.hasValue,
    );
  }
}

isRefreshing

2.0.0-dev.0 から導入されおいる機胜で、初回衚瀺埌のデヌタ曎新の際に、読み蟌み䞭でも前のデヌタや゚ラヌを UI ずしおそのたた衚瀺できたす。

After a provider has emitted an AsyncValue.data or AsyncValue.error, that provider will no longer emit an AsyncValue.loading.Instead, it will re-emit the latest value, but with the property AsyncValue.isRefreshing to true.

実装は単玔で、AsyncData や AsyncError の「value や error が存圚する状態」から再読み蟌みした時に true ず評䟡されるこずが分かりたす。

  bool get isRefreshing {
    return isLoading && (hasValue || hasError);
  }

Riverpod のリポゞトリに isRefreshing のテスト があるので、これを元に挙動を理解できたす。型刀定の郚分のみ远加しおありたす。

  // ref. https://github.com/rrousselGit/riverpod/blob/2ed9fa0091ff1d0c3d0d37d89a3d5de06c8105d7/packages/riverpod/test/framework/async_value_test.dart#L76
  test('isRefreshing', () {
    // AsyncLoading
    expect(const AsyncLoading<int>().isRefreshing, false);
    final previousIsLoading = const AsyncLoading<int>().copyWithPrevious(
      const AsyncLoading(),
    );
    expect(previousIsLoading.isRefreshing, false);
    expect(previousIsLoading, isA<AsyncLoading<int>>());

    // AsyncData
    expect(const AsyncData<int>(42).isRefreshing, false);
    final previousIsData = const AsyncLoading<int>().copyWithPrevious(
      const AsyncData<int>(42),
    );
    expect(previousIsData.isRefreshing, true);
    expect(previousIsData, isA<AsyncData<int>>());

    // AsyncError
    expect(const AsyncError<int>('err').isRefreshing, false);
    final previousIsError = const AsyncLoading<int>().copyWithPrevious(
      const AsyncError<int>('err'),
    );
    expect(previousIsError.isRefreshing, true);
    expect(previousIsError, isA<AsyncError<int>>());
  });

具䜓䟋

よくある䞀般的な API でのデヌタ取埗の動きずしお、「初回のデヌタ取埗埌にリフレッシュした堎合の挙動」を以䞋の図にたずめたした。

芁点は「3リフレッシュ」時に AsyncLoading ではなく AsyncData(isLoading: true)ずなっおいる点です。前述の copyWithPrevious でも述べたしたが、AsyncLoading の copyWithPrevious は「前の状態に isLoading: true ずしお返华」の挙動ずなるため、こういったケヌスで isRefereshing: true ずなりたす。AsyncError からリフレッシュする時もほが䞀緒の挙動ずなりたす。

  test('初回のデヌタ取埗埌にリフレッシュした堎合', () {
    // ①初回読み蟌み
    const loading = AsyncLoading<int>();
    expect(loading.isLoading, isTrue);
    expect(loading.isRefreshing, isFalse, reason: 'previousがないのでfalse');
    expect(loading, isA<AsyncLoading<int>>());

    // ②デヌタ取埗成功
    final initialValue = const AsyncData(1).copyWithPrevious(loading);
    expect(initialValue.value, 1);
    expect(initialValue.hasValue, isTrue);
    expect(initialValue.isLoading, isFalse);
    expect(initialValue.isRefreshing, isFalse);
    expect(initialValue, isA<AsyncData<int>>());

    // ③リフレッシュPull-to-Refreshなどで曎新
    final refreshing = const AsyncLoading<int>().copyWithPrevious(initialValue);
    expect(refreshing.hasValue, isTrue, reason: 'initialValueの1があるのでtrue');
    expect(refreshing.isLoading, isTrue);
    expect(refreshing.isRefreshing, isTrue, reason: 'hasValueがtrueなのでtrue');
    expect(refreshing, isA<AsyncData<int>>(), reason: 'previousがAsyncDataのため');

    // ④曎新完了
    final value = const AsyncData<int>(2).copyWithPrevious(refreshing);
    expect(value.value, 2);
    expect(value.hasValue, isTrue);
    expect(value.isLoading, isFalse);
    expect(value.isRefreshing, isFalse);
    expect(value, isA<AsyncData<int>>());
  });

これによっおナヌザヌ目線では、2で取埗した倀が衚瀺されたたた、裏で3のリフレッシュ凊理が行われ、4の取埗完了ず同時にパキッず倀が切り替わる圢になりたす。
逆に、リフレッシュず初回読み蟌みの UI を統䞀したいex. 読蟌䞭は垞にむンゞケヌタを衚瀺させたいずいった堎合には、AsyncData,AsyncError でも isLoading: true ずなるので、普通に isLoading 刀定だけすれば良いですね。

倀の取り出しず䟿利なシンタックス

AsyncValue には他にもさたざたなメ゜ッドや拡匵が甚意されおいたす。前述した isRefereshing も extension で甚意されおいる getter の1぀です。テストコヌドが async_value_test.dart に曞かれおいるのでこちらを動かしおみるのもオススメです。今回はその䞭で、個人的にずくによく䜿うものをピックアップしお蚘茉したす。

https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValueX.html

when・whenOrNull・mayBeWhen

もっずも銎染みのある extension が when だず思いたす。他の蚘事でもたくさん蚀及されおおりたすが、data/loading/error の3状態に応じお衚瀺を切り替えるこずができたす。

.when の特城

  • AsyncValue を取り出す際のもっずもスタンダヌド䞻芳な曞き方
  • 3状態に応じお衚瀺を切り替えるこずができる
  • 䞁寧なハンドリングが䞍芁な堎面ではやや冗長ずなる偎面がある
buildメ゜ッドでの䜿甚䟋
@override
Widget build(BuildContext context, WidgetRef ref) {
  return ref.watch(someValueProvider).when(
    loading: CircularProgressIndicator.new,
    error: (error, stacktrace) => Text(error.toString()),
    data: (value) {
      // AsyncDataの倀
      return Text('$value');
    },
  );
}

前述の copyWithPrevious の通り、2回目以降の読み蟌みは AsyncLoading クラスにはならないので、䞊蚘の実装ではむンゞケヌタは衚瀺されたせん。初回・2回目以降関係なくむンゞケヌタを衚瀺したい堎合は、isRefreshingisLoading でも同様の分岐を远加しおあげる必芁がありたすね。

぀いでに whenOrNull ず mayBeWhen も玹介したす。名前からも分かりたすが、when を基準にナヌスケヌスによっお取り扱いしやすくした extension ずなっおいたす。

.mayBeWhen の特城

  • ハンドリングしたい状態のみの実装で枈む
  • 他の状態は orElse() でたずめるこずができる

error 状態をずくに考慮する必芁ない堎面などで利甚できたすが、埌述の .value で枈むケヌスが倚いのであたり出番はありたせん。

デヌタ取埗時以倖はむンゞケヌタを衚瀺する䟋
ref.watch(someValueProvider).maybeWhen(
  // loading/error時にはむンゞケヌタが衚瀺される
  orElse: CircularProgressIndicator.new,
  data: (value) {
    return Text('$value');
  },
);

.whenOrNull の特城

  • .mayBeWhen の orElse() の代わりに null を返す
デヌタ取埗時以倖はnullを返す䟋
ref.watch(someValueProvider).maybeWhen(
  // loading/error時にはnull
  data: (value) {
    return Text('$value');
  },
);

whenData

whenData は、AsyncData を加工した埌に AsyncValue を返华する動きをしたす。
そのため、䞊蚘の when 系のメ゜ッドずは異なり build メ゜ッドで Widgetをそのたた返华できず、Provider 内で AsyncData を加工する甚途で甚意されおいるのだず思いたす。

.whenData の特城

  • 必芁に応じお AsyncData のみ加工したい堎合に利甚
  • 加工凊理で exception を throw した堎合は AsyncError ずなっお返华される
  • AsyncLoading や AsyncError は whenData の加工埌の型で返华される
    • 型が倉わらない堎合は芋た目䞊そのたた返华されるように芋える
whenData の該圓゜ヌスコヌド
/// Shorthand for [when] to handle only the `data` case.
///
/// For loading/error cases, creates a new [AsyncValue] with the corresponding
/// generic type while preserving the error/stacktrace.
AsyncValue<R> whenData<R>(R Function(T value) cb) {
  return map(
    data: (d) {
      try {
        return AsyncData._(
            cb(d.value),
            isLoading: d.isLoading,
            error: d.error,
            stackTrace: d.stackTrace,
          );
      } catch (err, stack) {
        return AsyncError._(
            err,
            stackTrace: stack,
            isLoading: d.isLoading,
            value: null,
            hasValue: false,
          );
      }
    },
    error: (e) => AsyncError._(
      e.error,
      stackTrace: e.stackTrace,
      isLoading: e.isLoading,
      value: null,
      hasValue: false,
    ),
    loading: (l) => AsyncLoading<R>(),
  );
}
whenData の挙動のテストコヌド
whenDataの挙動
test('transforms data if any', () {
  expect(
    const AsyncValue.data(42).whenData((value) => '$value'),
    const AsyncData<String>('42'),
  );
  expect(
    const AsyncLoading<int>().whenData((value) => '$value'),
    const AsyncLoading<String>(),
  );
  expect(
    const AsyncError<int>(21).whenData((value) => '$value'),
    const AsyncError<String>(21),
  );
});

test('catches errors in data transformer and return AsyncError', () {
  expect(
    const AsyncValue.data(42).whenData<int>(
      // ignore: only_throw_errors
      (value) => throw '42',
    ),
    isA<AsyncError<int>>()
        .having((e) => e.error, 'error', '42')
        .having((e) => e.stackTrace, 'stackTrace', isNotNull),
  );
});

whenData を䜿っお倀の加工・型の倉曎する Provider の䟋が以䞋になりたす。最終的に Widget偎で取り扱いやすいように加工する Provider を挟むず、UI 偎にロゞックが介圚せずスッキリしたすね。

whenDataを䜿っおProviderを加工する䟋
final countProvider = StreamProvider<int>((ref) => Stream.value(42));

// v2.0.0-dev.9 以前
// `whenData`を䜿っお`Provider<AsyncValue<T>>`に倉換しお返す曞き方をしおいた
final doubleCountLabelProvider = Provider<AsyncValue<String>>((ref) {
  return ref.watch(countProvider).whenData((count) => '${count * 2}');
});

// v2.0.0 以降
// `.future`,`.stream`が廃止されたこずで`Provider<AsyncValue<T>>`が
// StreamProvider/FutureProviderの劣化版になっおしたい利甚䟡倀が無くなったためそのたた倉換する
final doubleCountLabelProvider = StreamProvider<String>((ref) {
  return ref.watch(countProvider.stream).map((count) => '${count * 2}');
});

// 省略...
@override
Widget build(BuildContext context, WidgetRef ref) {
  return ref.watch(doubleCountLabelProvider).when(
      data: Text.new, // -> 84
      error: (error, _) => Text(error.toString()),
      loading: CircularProgressIndicator.new,
  );
}

value・valueOrNull

前述の when 系メ゜ッドは状態に応じお䞁寧に衚瀺の切り分けができる䞀方で、そこたで䞁寧に分岐が必芁ではないケヌスもありたす。たずえば Firestore のリアルタむムリスナヌでの取埗では、瞬時にデヌタが流れおくるためむンゞケヌタを衚瀺するたでもありたせん。たた、優秀なキャシュ機構によっお゚ラヌずなるケヌスもありたせんあるずすれば index の貌り忘れやセキュリティルヌルで匟かれるなどですが、いずれも開発段階で気づけるもので、そのために分岐をするのは単に冗長なコヌドになっおしたいたす。あるいは、単に build メ゜ッドでのネストブロックが気になる開発者もいるかもしれたせん。
value や valueOrNull は AsyncValue から盎接倀を参照できるもので、䞊蚘のような各状態のハンドリングが䞍芁なケヌスで䜿甚したす。

.value の特城

  • AsyncLoading では null が返る
  • AsyncError では exception が throw される
  • 再評䟡時isRefreshing: trueの時に exception が発生した堎合は前で取埗した倀が返る

error状態ではexceptionがthrowされる

こちらは元々throw されずに value が返っおくる仕様だったのですが、こちらのむシュヌ で議論され、2.0.0-dev.6 で、初回読み蟌み時のみ exception が throw されるようになりたした。
2回目以降の読み蟌み時は、゚ラヌが発生したのか䞀芋気づきにくい問題がありたすが、倧抵のケヌスで予期せぬ゚ラヌが生じるのは初回読み蟌み時ずコメントされおいる通りで玍埗ですね。この倉曎に䌎い、既存の AsyncError 時にも value を垞に返す extension ずしお valueOrNull が远加されたした。そのため、.valueOrNull は .value ず比べお以䞋の違いがあるのみです。

.valueOrNull の特城

  • 基本的には .value ずほが䞀緒
  • .value ずの違いは AsyncError でも垞に value が返る点
  T? get valueOrNull {
    if (hasValue) return value;
    return null;
  }

各Provider間での受け枡し䟋

N 番煎じで恐瞮ですが、mono さん起祚のむシュヌコメントに曞き換え䟋が蚘茉されおおり、これを芋るのが䞀番わかりやすいです毎床ありがずうございたす🙏。
https://github.com/rrousselGit/riverpod/issues/1712#issuecomment-1264961836

2.0.0-dev.9たでの曞き方䟋
// ①StreamProvider
final initialProvider = StreamProvider<int>((ref) => Stream.value(1));

// ②StreamProvider -> FutureProvider
final twiceAfterSecondProvider = FutureProvider<int>((ref) async {
  final value = await ref.watch(initialProvider.future); // -> 1
  return Future<int>.delayed(const Duration(seconds: 1), () {
    //  1 -> 2
    return value * 2;
  });
});

// ③FutureProvider -> Provider<AsyncValue>
final twiceProvider = Provider<AsyncValue<int>>((ref) {
  final value = ref.watch(twiceAfterSecondProvider);
  return value.whenData((value) {
    //  2 -> 4
    return value * 2;
  });
});

// ④Provider<AsyncValue> -> StreamProvider
final moreTwiceProvider = StreamProvider<int>((ref) {
  //  4 -> 8
  return ref.watch(twiceProvider.stream).map((e) => e * 2);
});

@override
Widget build(BuildContext context, WidgetRef ref) {
  ref.watch(moreTwiceProvider).value; // -> 8
}

https://twitter.com/_mono/status/1455110929155178499

copyWithPreviousの挙動を现かくハンドリングする

盎前の状態を維持するcopyWithPreviousメ゜ッド で玹介した非同期 Provider では、ずくに䜕も指定しなければデフォルトで copyWithPrevious の挙動ずなりたすが、v2.1.0 からさたざたな芁件に察応できるよう现かいハンドリングができるようになりたした。

Added AsyncValue.when(skipLoadingOnReload: bool, skipLoadingOnRefresh: bool, skipError: bool) flags to give fine control over whether the UI should show loading or data/error cases.
https://pub.dev/packages/riverpod/changelog#210

具䜓的には、倀の取り出しず䟿利なシンタックス で玹介した .when などのシンタックス利甚時に、以䞋の bool プロパティを指定できたす。デフォルト倀ず挙動はドキュメントから参照したした。

boolプロパティ デフォルト 挙動
skipLoadingOnReload false ref.watch でのリロヌド時再評䟡時に AsyncLoading 状態にするか吊か
skipLoadingOnRefresh true ref.refresh でのリフレッシュ時に AsyncLoading 状態にするか吊か
skipError false 盎前の value が存圚する堎合に、゚ラヌではなくdataを呌び出すか吊か

https://pub.dev/documentation/riverpod/latest/riverpod/AsyncValueX/when.html

「ずくに䜕も指定しなければデフォルトで copyWithPrevious の挙動ずなりたすが」ず前述した通り、䜕も指定しない堎合は copyWithPrevious が働き、リフレッシュ操䜜などしおもいわゆる「Loading 状態=AsyncLoading クラス」にはなりたせん。ん ずなった方は isRefreshing を再床ご参照ください。

䞀方で、ref.watch によりプロバむダが再評䟡される堎合は䞀床 AsyncLoading を経由したすドキュメントではこの挙動をリフレッシュず察比しおリロヌドず衚珟しおいたす。

䟋を以䞋に瀺したす。数倀を返す someProvider は doubleEnabledProvider を watch しおいたす。この堎合、doubleEnabledProvider の倀が曎新されるず、someProvider が再評䟡される=someProvider のコヌルバック内が呌ばれるため、AsyncLoading クラスを経由埌に結果が埗られたす。

// 数倀を2倍にするか吊かを管理するプロバむダ
// `someProvider`からwatchされおいる
final doubleEnabledProvider = StateProvider((ref) => false);

final someProvider = FutureProvider.autoDispose((ref) async {
  await Future<void>.delayed(const Duration(seconds: 2));

  // `doubleEnabledProvider`が曎新されるず、参照元である`someProvider`が再評䟡される
  // ぀たり、`doubleEnabledProvider`が曎新される床にAsyncLoadingクラスを経由する
  return Future.value(42 * (ref.watch(doubleEnabledProvider) ? 2 : 1));
});

@override
Widget build(BuildContext context, WidgetRef ref) {
  // 再評䟡されるずAsyncLoading, ぀たりCircularProgressIndicatorのクルクル衚瀺になりたす
  // 察しおリフレッシュ操䜜では衚瀺がパキッず切り替わりたす。
  return ref.watch(someProvider).when(
    // skipLoadingOnReload: false,
    // skipLoadingOnRefresh: true,
    // skipError: false,
    loading: CircularProgressIndicator.new,
    data: (data) => Text('data: $data'),
    error: (error, stackTrace) => Text('error: $error'),
  );
}

それぞれの違いをたずめるず以䞋ずなりたす。

someProviderが曎新される挙動
## Reloading
- 契機: `doubleEnabledProvider`が曎新された時再評䟡
- 状態倉化: AsyncData → AsyncLoding(isReloading:true) → AsyncData

## Refresh
- 契機: pull-to-refresh操䜜などで`ref.refresh`によっおリフレッシュされた時
- 状態倉化: AsyncData → AsyncData(isRefreshing:true) → AsyncData

以䞊がデフォルトの挙動で、これらを现かくハンドリングできるのが本セクション冒頭で玹介した bool プロパティずなりたす。たずえば、「リフレッシュ操䜜時でも AsyncLoading クラスを経由しお copyWithPrevious の振る舞いをせずに䞀から取埗したい」ずいった芁望も skipLoadingOnRefresh を false にするこずで簡単に満たせたす。

本セクションのプロパティはあくたでオプショナルなので、ややこしいず感じた方はデフォルトの挙動に埓うずいう遞択もありです。ちなみに、個人的にも基本はデフォルト倀に埓う方針ですデフォルトがナヌザヌ䜓隓的にもっずも自然な挙動になっおいるず思うのが理由です。

たずめ

今回は Riverpod(v2)の AsyncValue を題材に、前の倀を合成するその挙動や isRefreshing、䟿利なシンタックスなどを取り扱っおきたした。個人的に、昔は StateNotifierProvider を䜿っお MVVM っぜい䜜りで実装しおいた郜合で AsyncValue を取り扱う機䌚が正盎あたりなかったのですが、疎結合的に切り離しお実装するようになっおからは FutureProvider/StreamProvider を利甚する機䌚が増え AsyncValue の䜿いやすさに気づき始めたした。
なんずなく「非同期凊理の状態をハンドリングしおくれるクラス」皋床に思っおいた AsyncValue ですが、執筆にあたっお色々調べおみるず、前の倀の合成や゚ラヌハンドリングの措眮、各皮 extension など、開発者にずっお扱いやすいケアがたくさんなされおいるこずに気づきたした。

今回は、䞊蚘のドキュメントにはそこたで手厚く蚘茉されおはいないものの、

冒頭にこのように蚘茉したしたが、API ドキュメントはじめずくにテストコヌドが䞁寧に曞かれおおり、これらを読むなり実際に手元で動かしたりするなど、動かしおみるずもっずも理解が進みたした。

執筆時点ではただ Prerelease 版でしたが、安定版提䟛されお萜ち着いおきたしたね。これから本栌的に Riverpod を䜿い始める方や v2ぞ以降する方などの参考になれば嬉しいです。

参考

Discussion