Flutter Provider访问新分类时先加载旧分类列表数据异常
问题表现
- 分类跳转商品列表功能存在渲染异常:从分类详情页返回列表后,点击其他分类进入详情页时,页面会先闪显上一个分类的旧商品数据,等待接口返回后才会刷新为当前分类的正确数据。
- 已在页面
dispose生命周期中调用_product.clear(),问题未解决。
原有问题代码
class _CategoryDetailsScreenState extends State<CategoryDetailsScreen> { late List _product; @override void initState() { //ModalRoute not work in init state without future delayed Future.delayed(Duration.zero).then((_) { var _args = ModalRoute.of(context)!.settings.arguments as CategoryDetailsScreenArgs; Provider.of<ProductsProvider>(context,listen:false).fetchAndSetCatProducts(catid: _args.id, page: page); } } @override void dispose() { _product.clear(); super.dispose(); } @override Widget build(BuildContext context){ _product = Provider.of<ProductsProvider>(context).products; return Scaffold( body: _product.isEmpty? WidgetOne() : ProductWidget() ) } }
问题根因
- 数据清空时机完全错误。旧详情页的
dispose生命周期执行时间晚于新详情页的首次渲染时间:新页面push入栈时会先执行新页面的initState、完成首次build渲染,之后才会执行旧页面的dispose逻辑。在旧页销毁时才清空列表,新页第一次渲染时早就从Provider里读到了残留的旧数据,直接就把旧数据渲染到页面上了。 - 新数据拉取存在不必要的延迟。
initState里用Future.delayed(Duration.zero)包裹拉取逻辑,这个异步任务会等当前帧渲染完成才执行,等于新页面第一次build的时候,新分类的数据拉取根本还没触发,自然只能读到旧数据。 - 冗余的本地状态字段增加了状态同步风险。State里定义的
_product只是对Provider中全局列表的引用,本地持有这个字段没有实际作用,反而容易因为引用传递问题出现清空操作不生效的情况。
修复方案
- 把旧数据清空逻辑从
dispose迁移到数据拉取方法的入口处:触发新分类数据请求前,先清空Provider中存储的旧商品列表,立刻通知监听方刷新,保证页面首次渲染时拿到的是空状态,不会展示旧数据。 - 去掉无意义的
Future.delayed,改用addPostFrameCallback获取路由参数后直接触发拉取,减少不必要的时序延迟;如果业务允许,直接把分类id通过页面构造函数传递,完全可以避免路由参数获取的时序问题。 - 删除State中冗余的
_product字段,直接在build方法中从Provider读取最新数据即可,不需要在本地缓存引用。
修正后页面代码
class _CategoryDetailsScreenState extends State<CategoryDetailsScreen> { // 保留原有分页参数 int page = 1; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { final args = ModalRoute.of(context)!.settings.arguments as CategoryDetailsScreenArgs; Provider.of<ProductsProvider>(context, listen: false) .fetchAndSetCatProducts(catid: args.id, page: page); }); } // 移除dispose中清空列表的无效逻辑 @override Widget build(BuildContext context) { // 直接从Provider读取最新数据,不存本地字段 final productList = Provider.of<ProductsProvider>(context).products; return Scaffold( body: productList.isEmpty ? const WidgetOne() : const ProductWidget(), ); } }
Provider拉取方法补充逻辑
Future<void> fetchAndSetCatProducts({required int catid, required int page}) async { // 请求发起前先清空旧数据,通知页面刷新为空状态 products.clear(); notifyListeners(); // 保留原有接口请求、解析新数据、赋值products、notifyListeners的逻辑 }
内容的提问来源于stack exchange,提问作者Abu Sayed
相关产品推荐
相关产品推荐

