rrousselGit / riverpod

A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
https://riverpod.dev
MIT License
6.28k stars 955 forks source link

Refreshing state for FutureProvider #333

Closed derolf closed 3 years ago

derolf commented 3 years ago

Currently, FutureProvider‘s AsyncValue had 3 states. It’s possible to retrigger the Future by using refresh.

However, that way the data is lost since the FP is reset.

For proper pull-to-refresh you need a fourth state “refreshing” that lets you access the “stale” data that was available before calling refresh.

rrousselGit commented 3 years ago

That would be the responsibility of the widget doing the pull-to-refresh

The widget should keep a cache on the latest value and use it during loading or error status.

derolf commented 3 years ago

I know, but then the logic in the widget get’s quite complex since using “when” on the value will go to the “loading” branch, but your content widget usually sits in the “data” branch.

Also, you might have quite a few places watching the same FutureProvider.

i thought it would be nicer to generalize it on the riverpod side.

rrousselGit commented 3 years ago

I agree. But I still don't think that that AsyncValue is the correct place

This is the typical use-case for hooks.

For example you could do:

AsyncValue<T> useLastValidAsyncValue<T>(AsyncValue<T> value) {
  return useValueChanged<AsyncValue<T>, AsyncValue<T>>(
    value,
    (_, previousValidValue) {
      return value.data ?? previousValidValue;
    },
  );
}

It then allows:

Widget build(context) {
  final AsyncValue<int> value = useProvider(futureProvider);
  // after calling `context.refresh(futureProvider)`,
  // lastValueValid will contain the last data instead of loading state
  final AsyncValue<int> lastValueValid = useLastValidAsyncValue(value);

  // TODO return lastValueValue.when(...)
}
rrousselGit commented 3 years ago

You could easily tweak this hook to show snackbars on error after a pull-to-refresh too.

fpoppinga commented 3 years ago

We are using the following hook very successfully to access the last update to a FutureProvider while a new update is being processed. This is very handy when dealing e.g. with Firestore live updates.

/// useProviderCached returns the freshest non-loading [AsyncValue] from a
/// [ProviderListenable].
AsyncValue<T> useProviderCached<T>(ProviderListenable<AsyncValue<T>> provider) {
  final value = useProvider(provider);
  final cache = useState<AsyncValue<T>>(value);

  if (!(value is AsyncLoading)) {
    cache.value = value;
    return value;
  }

  if (cache.value != null && !(cache.value is AsyncLoading)) {
    return cache.value;
  }

  cache.value = value;
  return value;
}
derolf commented 3 years ago

Thanks @fpoppinga

derolf commented 3 years ago

For anyone else who needs this, I have put useLastValidAsyncData.html into my noob package: https://pub.dev/documentation/noob/latest/noob/useLastValidAsyncData.html