Flutter REST API分页异常:数据加载完成后重复加载无提示
Flutter分页加载重复数据且不显示“已加载全部”的问题修复
问题描述
Flutter应用中通过HTTP调用REST API实现分页功能,基础分页逻辑可运行,但所有数据加载完成后,未显示“已加载全部数据”提示,反而重复加载顶部旧数据,持续生成重复内容。
错误原因分析
- API请求参数格式错误:原请求URL未指定分页参数名(如
page、limit),后端无法正确识别分页参数,导致每次请求都返回第一页数据。 - 无效的重复数据校验:
if (!fetchedPosts.contains(fetchedPosts))逻辑错误,列表必然包含自身,导致所有重复数据都会被添加到列表中。 - 分页终止逻辑未触发:由于每次请求都返回非空的第一页数据,
_hasNextPage永远不会被设为false,“已加载全部”提示无法显示。
修复步骤
1. 修正API请求URL
为分页参数添加明确的参数名,确保后端能正确解析:
// 原代码 final res = await http.get(Uri.parse("$_baseUrl?$_page&$_limit")); // 修改后 final res = await http.get(Uri.parse("$_baseUrl?page=$_page&limit=$_limit"));
2. 删除无效的重复数据判断
移除逻辑错误的重复校验代码,直接添加新获取的数据:
// 原代码 if (fetchedPosts.isNotEmpty) { if (!fetchedPosts.contains(fetchedPosts)) { setState(() { _posts.addAll(fetchedPosts); }); } } // 修改后 if (fetchedPosts.isNotEmpty) { setState(() { _posts.addAll(fetchedPosts); }); }
3. 确保分页终止逻辑正常触发
当后端返回空列表时,正确设置_hasNextPage = false,停止后续加载请求:
else { setState(() { _hasNextPage = false; }); }
完整修复后的代码
import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_web_browser/flutter_web_browser.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'dart:convert'; import "package:http/http.dart" as http; import 'package:voterlist/Widgets/adhelper.dart'; import 'package:voterlist/Widgets/constant.dart'; import 'package:voterlist/Widgets/skeleton.dart'; import 'package:voterlist/Widgets/widget.dart'; class Jobs extends StatefulWidget { const Jobs({Key? key}) : super(key: key); @override State<Jobs> createState() => _JobsState(); } class _JobsState extends State<Jobs> { final adManager = AdManager(); // 数据接口地址 final _baseUrl = 'api'; // 初始页码 int _page = 0; // 每页加载数量 final int _limit = 15; // 是否还有下一页数据 bool _hasNextPage = true; // 首次加载状态标记 bool _isFirstLoadRunning = false; // 加载更多状态标记 bool _isLoadMoreRunning = false; // 存储获取到的数据 List _posts = []; // 首次加载数据 void _firstLoad() async { setState(() { _isFirstLoadRunning = true; }); try { // 修复:添加分页参数名 final res = await http.get(Uri.parse("$_baseUrl?page=$_page&limit=$_limit")); setState(() { _posts = json.decode(res.body); }); } catch (err) { if (kDebugMode) { print('请求失败'); } } setState(() { _isFirstLoadRunning = false; }); } // 滚动到底部时加载更多数据 void _loadMore() async { if (_hasNextPage == true && _isFirstLoadRunning == false && _isLoadMoreRunning == false && _controller.position.extentAfter < 300) { setState(() { _isLoadMoreRunning = true; // 显示底部加载指示器 }); _page += 1; // 页码+1 try { // 修复:添加分页参数名 final res = await http.get(Uri.parse("$_baseUrl?page=$_page&limit=$_limit")); final List fetchedPosts = json.decode(res.body); if (fetchedPosts.isNotEmpty) { // 修复:删除无效的重复判断 setState(() { _posts.addAll(fetchedPosts); }); } else { // 没有更多数据,终止分页 setState(() { _hasNextPage = false; }); } } catch (err) { if (kDebugMode) { print('加载更多失败'); } } setState(() { _isLoadMoreRunning = false; }); } } // 列表滚动控制器 late ScrollController _controller; @override void initState() { super.initState(); _firstLoad(); _controller = ScrollController()..addListener(_loadMore); adManager.addAds(true, true, true); adManager.showInterstitial(); } @override void dispose() { _controller.removeListener(_loadMore); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: headerNav(title: "Sarkari Naukri"), body: _isFirstLoadRunning ? ListView.separated( itemCount: 5, itemBuilder: (context, index) => const NewsCardSkelton(), separatorBuilder: (context, index) => const SizedBox(height: defaultPadding), ) : Column( children: [ Expanded( child: ListView.separated( controller: _controller, itemCount: _posts.length, itemBuilder: (_, index) => JobCard( onPress: () { FlutterWebBrowser.openWebPage( url: _posts[index]['moredetils'], safariVCOptions: const SafariViewControllerOptions( barCollapsingEnabled: true, preferredBarTintColor: Colors.green, preferredControlTintColor: Colors.amber, dismissButtonStyle: SafariViewControllerDismissButtonStyle.close, modalPresentationCapturesStatusBarAppearance: true, ), ); }, image: _posts[index]['imagelink'], state: _posts[index]['state'], title: _posts[index]['title'], subtitle: _posts[index]['totalpost'].toString(), ), separatorBuilder: (BuildContext context, int index) { if ((index + 1) % 4 == 0) { return Container( width: adManager.getBannerAd()?.size.width.toDouble(), height: adManager.getBannerAd()?.size.height.toDouble(), child: AdWidget(ad: adManager.getBannerAd()!), ); } return const SizedBox(); }, ), ), // 加载更多时显示进度指示器 if (_isLoadMoreRunning == true) const Padding( padding: EdgeInsets.only(top: 10, bottom: 40), child: Center( child: CircularProgressIndicator(), ), ), // 没有更多数据时显示提示 if (_hasNextPage == false) Container( padding: const EdgeInsets.only(top: 30, bottom: 40), color: Colors.amber, child: const Center( child: Text('已加载全部职位'), ), ), ], ), ); } } class NewsCardSkelton extends StatelessWidget { const NewsCardSkelton({ Key? key, }) : super(key: key); @override Widget build(BuildContext context) { return Row( children: [ const Skeleton(height: 120, width: 120), const SizedBox(width: defaultPadding), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Skeleton(width: 80), const SizedBox(height: defaultPadding / 2), const Skeleton(), const SizedBox(height: defaultPadding / 2), const Skeleton(), const SizedBox(height: defaultPadding / 2), Row( children: const [ Expanded( child: Skeleton(), ), SizedBox(width: defaultPadding), Expanded( child: Skeleton(), ), ], ) ], ), ) ], ); } }
内容的提问来源于stack exchange,提问作者Nikhil27bYt
相关产品推荐
相关产品推荐

