You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 08:25:37