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

Riverpod:正确实现无限加载Provider的技术咨询

Riverpod无限滚动加载问题修复方案

针对你遇到的三个问题,逐一给出解决方法:

1. 正确追加数据到state

你的核心问题是state是AsyncValue类型,不是普通List,直接用state = [...state, ...awaiteddata]会报错,因为无法直接对AsyncValue做数组展开操作。

推荐用你已经定义的RecipeResponse作为Notifier的state类型,这样可以同时存储全部数据和当前页码,更符合无限滚动的状态管理需求。修改后的Provider代码如下:

@riverpod
class RecipeData extends _$RecipeData {
  final int _limit = 8;

  @override
  Future<RecipeResponse> build() async {
    final data = await RecipeService.fetchFromApi(ref, _limit, 0);
    return RecipeResponse(alldata: data, page: 1);
  }

  Future<void> fetchNextBatch() async {
    // 先处理重复请求判断,下文会讲
    if (state.isLoading) return;

    // 获取当前页码和已有数据
    final currentState = state.valueOrNull ?? RecipeResponse();
    final nextPage = currentState.page + 1;
    final offset = (nextPage - 1) * _limit;

    // 标记加载状态
    state = AsyncLoading();
    try {
      final newData = await RecipeService.fetchFromApi(ref, _limit, offset);
      // 合并新旧数据,更新页码
      state = AsyncData(
        currentState.copyWith(
          alldata: [...currentState.alldata, ...newData],
          page: nextPage,
        ),
      );
    } catch (e, stackTrace) {
      // 恢复之前的状态并抛出错误
      state = AsyncError(e, stackTrace);
    }
  }

  void resetPage() {
    // 重置时重新触发build方法加载第一页
    ref.invalidateSelf();
  }
}

2. 防止重复请求的两种方式

  • 方式一:利用AsyncValue的isLoading状态
    在fetchNextBatch开头直接判断:

    if (state.isLoading) return;
    

    这样当正在加载下一页时,重复调用该方法会直接返回,避免重复请求。

  • 方式二:添加本地状态标记
    如果需要更精细的控制,可以在Notifier内部添加一个_isFetching变量:

    bool _isFetching = false;
    
    Future<void> fetchNextBatch() async {
      if (_isFetching) return;
      _isFetching = true;
      try {
        // 请求逻辑...
      } finally {
        _isFetching = false;
      }
    }
    

    这种方式适合区分"首屏加载"和"分页加载"的状态场景。

3. 页码的存储方式选择

不建议改用StateProvider,原因如下:

  • 页码和Recipe数据是强关联的状态,放在同一个Notifier内部可以保证状态一致性,避免分散管理导致的同步问题。
  • 使用StateProvider会额外增加监听者的重建次数:每次页码变化时,所有监听该StateProvider的组件都会重建,而放在Notifier内部只有当最终的数据(RecipeResponse)更新时才会通知监听者,减少不必要的UI重建。
  • 你已经定义了RecipeResponse模型,正好可以把页码和数据封装在一起,逻辑更清晰。

修改后的Widget使用示例

@override
Widget build(BuildContext context) {
  final recipes = ref.watch(recipeDataProvider);

  return Scaffold(
    appBar: const AppBar(title: Text('Playground')),
    body: recipes.when(
      data: (response) => ListView.builder(
        itemCount: response.alldata.length + 1, // 多一个加载更多的item
        itemBuilder: (context, index) {
          if (index == response.alldata.length) {
            // 加载更多的触发逻辑,比如滚动到底部时调用fetchNextBatch
            return TextButton(
              onPressed: () => ref.read(recipeDataProvider.notifier).fetchNextBatch(),
              child: const Text('加载更多'),
            );
          }
          final recipe = response.alldata[index];
          return ListTile(
            title: Text(recipe.name),
            subtitle: Text(recipe.cuisine),
            leading: Image.network(recipe.image),
          );
        },
      ),
      loading: () => const Center(child: CircularProgressIndicator()),
      error: (err, _) => Center(child: Text(err.toString())),
    ),
  );
}

内容的提问来源于stack exchange,提问作者enchance

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 18:33:13