如何在Riverpod中获得提供商的先前价值



下面的代码接受用户输入,并在延迟1秒后以大写打印。

最小可复制代码:

class FooPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncValue = ref.watch(resultProvider);
print('loading: ${asyncValue.isLoading}');
return Scaffold(
body: Column(
children: [
TextField(onChanged: (s) => ref.read(queryProvider.notifier).state = s),
asyncValue.when(
data: Text.new,
error: (e, s) => Text('Error = $e'),
loading: () => Text('Loading...'),
),
],
),
);
}
}
final stringProvider = FutureProvider.family<String, String>((ref, query) async {
await Future.delayed(Duration(seconds: 1));
return query.toUpperCase();
});
final queryProvider = StateProvider<String>((ref) => '');
final resultProvider = FutureProvider<String>((ref) async {
final query = ref.watch(queryProvider);
return ref.watch(stringProvider(query).future);
});

运行代码后,

  1. 输入任意文本(如a(并等待输出(大写(
  2. 输入另一个文本(比如b(,然后等待输出(大写(
  3. 按下退格键(即删除字符b(,现在控制台将打印loading: true,因此加载小部件只需几秒钟。这会导致用户体验非常差

此问题发生在最新的2.0.2版本上。那么,我如何获得以前的值,以便在获取数据后能够一致地显示data呢?


更新:

这就是我使用git:的方式

dependencies:
flutter:
sdk: flutter
flutter_riverpod:
git:
url: https://github.com/rrousselGit/riverpod/tree/master/packages/flutter_riverpod
ref: master 

2.1.0版本包含额外的实用程序,用于在加载过程中获取以前的值/错误。

注意发布时,2.1.0当前尚未发布。如果你想使用它,你可以在你的pubspec中使用git依赖项来做:

# Using dependency_overrides is necessary due to flutter_riverpod depending on riverpod
dependency_overrides:
flutter_riverpod:
git:
url: https://github.com/rrousselGit/riverpod/
ref: master
path: packages/flutter_riverpod
riverpod:
git:
url: https://github.com/rrousselGit/riverpod/
ref: master
path: packages/riverpod

现在可以在加载状态下获得以前的值/错误。

假设您将FutureProvider定义如下:

final counterProvider = StateProvider<int>((ref) => 0);
final futureProvider = FutureProvider<String>((ref) async {
await Future.delayed(Duration(seconds: 2));
return "Hello world ${ref.watch(counterProivder)}';
});

其中改变CCD_ 8使得CCD_。

有了这个片段,在2.1.0及更高版本中,如果futureProvider重新加载,您仍然可以访问value/error:

AsyncValue<String> state = ref.watch(futureProvider);
if (state.isLoading) {
// during loading state, value/errors are still available
print(state.value);
print(state.error);
}

当然,与漂亮的when语法相比,编写if (state.isLoading)有点不方便。

默认情况下,如果提供程序因Ref.refresh/Ref.invalidate而重建,则when语法已正确调用data/error,而不是loading

另一方面,如果Ref.watch故意触发重建(因为您的提供者的参数已经更改(,则不会执行此操作
但是when公开了用于更改这一点的标志。

更具体地说,你可以写:

AsyncValue<String> state = ref.watch(futureProvider);
state.when(
skipLoadingOnReload: true,
loading: () => print('loading'),
data: (value) => print('value: $value'),
error: (err, stack) => print('error: $err');
);

使用此代码,无论提供程序是通过Ref.watch重建还是通过Ref.refresh重建,when方法都将跳过loading,而是调用data/error

在将我的riverpod升级到2.0.2之后,我现在可以看到这种行为了。主要原因是延迟了1秒。除非消除延迟,否则无法消除加载,因为每次查询发生更改时,都会调用未来的提供程序,我尝试过了,它运行良好。现在,对于您的主要问题,即如何访问提供者的先前状态,您可以使用ref.listen()来完成此操作。下面是关于如何访问它的演示。

class FooPage extends ConsumerWidget {
const FooPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {

ref.listen<AsyncValue<String>>(resultProvider, (previous, next) {
//* you can access the previous state here
if (previous?.value != '' && previous?.value != null) {
ref.read(upperCaseQuery.notifier).state = previous?.value ?? '';
}
});
final resultValue = ref.watch(resultProvider);
final upperCased = ref.watch(upperCaseQuery.state).state;
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(onChanged: (s) {
ref.read(queryProvider.notifier).state = s;
}),
//previous value of the the provider
Text(upperCased),
resultValue.when(
data: (value) =>  Text(value),
error: (e, s) => Text('Error = $e'),
loading: () => const Text('Loading...'),
),
],
),
),
);
}
}

final queryProvider = StateProvider<String>((ref) => 'default');
final resultProvider = FutureProvider.autoDispose<String>((ref) async {
final query = ref.watch(queryProvider);
await Future.delayed(const Duration(seconds: 1));
final upperCased = query.toUpperCase();
return upperCased;
}, name: 'result provider');
final upperCaseQuery = StateProvider<String>((ref) => '', name: 'upper cased');

如果将await Future.delayed(const Duration(seconds: 1));stringProvider移动到resultProvider,一切都会如您所期望的那样工作。

此外,让我为您提供一个代码现代化的变体:

/// Instead `stringProvider`.
Future<String> stringConvert(String query) async {
await Future.delayed(const Duration(seconds: 1));
return query.toUpperCase();
}
final queryProvider = StateProvider<String>((ref) {
return '';
});
final resultProvider = FutureProvider<String>((ref) async {
final query = ref.watch(queryProvider);
return stringConvert(query);
});

最新更新