如何在Riverpod中获取Provider的旧值以优化加载体验?
问题:Riverpod输入变更时短暂显示加载状态,如何保留旧数据?
问题场景
代码功能为接收用户输入,延迟1秒后转换为大写展示,但操作时出现以下体验问题:
- 输入文本(如
a),等待后正常展示大写结果 - 输入另一文本(如
b),等待后正常展示大写结果 - 按下退格键删除字符时,控制台打印
loading: true,页面短暂显示加载组件,用户体验不佳
该问题出现在Riverpod 2.0.2版本(当前依赖使用master分支)。
最小可复现代码
Dart代码
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); });
依赖配置
dependencies: flutter: sdk: flutter flutter_riverpod: git: url: https://github.com/rrousselGit/riverpod/tree/master/packages/flutter_riverpod ref: master
解决方案
核心思路是在新数据加载完成前,保留并展示之前的成功数据,避免直接切换到loading状态,以下是两种可行实现:
方案1:修改Provider逻辑,缓存旧数据
调整resultProvider为StateProvider<AsyncValue<String>>,通过监听查询变化,在新请求未完成时保留旧数据:
final resultProvider = StateProvider<AsyncValue<String>>((ref) { final query = ref.watch(queryProvider); final previousState = ref.state; // 清空查询时直接重置状态 if (query.isEmpty) { return const AsyncData(''); } // 查询内容未变更时,直接返回旧状态 if (previousState is AsyncData && previousState.value.toLowerCase() == query) { return previousState; } // 发起新请求,请求完成后更新状态 ref.listen( stringProvider(query), (_, next) { ref.state = next; }, ); // 加载状态下保留旧数据,仅标记加载中 return previousState.when( data: (data) => AsyncData(data).copyWith(isLoading: true), error: (e, s) => AsyncError(e, s).copyWith(isLoading: true), loading: () => const AsyncLoading(), ); });
UI层对应调整,加载时显示旧数据+加载提示,而非直接替换为加载文本:
asyncValue.when( data: (data) => Row( children: [ Text(data), if (asyncValue.isLoading) const CircularProgressIndicator(size: 16), ], ), error: (e, s) => Text('Error = $e'), loading: () => const Text('Loading...'), // 仅初始加载时显示 )
方案2:UI层缓存旧数据
通过ConsumerStatefulWidget在UI层记录上次成功数据,加载时优先展示旧数据:
class FooPage extends ConsumerStatefulWidget { const FooPage({super.key}); @override ConsumerState<FooPage> createState() => _FooPageState(); } class _FooPageState extends ConsumerState<FooPage> { String? _lastData; @override Widget build(BuildContext context) { final asyncValue = ref.watch(resultProvider); // 更新缓存的旧数据 asyncValue.whenData((data) => _lastData = data); print('loading: ${asyncValue.isLoading}'); return Scaffold( body: Column( children: [ TextField(onChanged: (s) => ref.read(queryProvider.notifier).state = s), // 加载时且有旧数据则显示旧数据,否则按原逻辑处理 if (asyncValue.isLoading && _lastData != null) Text(_lastData!) else asyncValue.when( data: Text.new, error: (e, s) => Text('Error = $e'), loading: () => const Text('Loading...'), ), ], ), ); } }
内容的提问来源于stack exchange,提问作者iDecode
相关产品推荐
相关产品推荐

