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
相关产品推荐
相关产品推荐

