如何在 Riverpod 中获取提供商之前的值?

iDe*_*ode 1 flutter riverpod flutter-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);
});
Run Code Online (Sandbox Code Playgroud)

运行代码后,

  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 
Run Code Online (Sandbox Code Playgroud)

Rém*_*let 5

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
Run Code Online (Sandbox Code Playgroud)

现在可以在加载状态期间获取先前的值/错误。

假设您定义 aFutureProvider如下:

final counterProvider = StateProvider<int>((ref) => 0);

final futureProvider = FutureProvider<String>((ref) async {
  await Future.delayed(Duration(seconds: 2));
  return "Hello world ${ref.watch(counterProivder)}';
});
Run Code Online (Sandbox Code Playgroud)

其中更改counterProvider会导致futureProvider返回到加载状态。

使用此代码片段,在 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);
}
Run Code Online (Sandbox Code Playgroud)

当然,if (state.isLoading)相对于漂亮的语法来说,写起来有点不方便when

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

另一方面,如果重建是有意触发的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');
);
Run Code Online (Sandbox Code Playgroud)

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