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

Flutter中showDialog无限重建问题求助

问题描述

点击带分页功能的订单列表项弹出Dialog后,Dialog的builder函数会无限打印"inside"(即Dialog无限重建),相关代码位于一个Stateful Widget中:

final _scrollController = ScrollController();

@override
void initState() {
  _scrollController.addListener(() {
    pagination();
  });
  super.initState();
}

void pagination() {
  var len =
      Provider.of<OrderHistoryData>(context, listen: false).allOrders.length;
  if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent &&
      !Provider.of<OrderHistoryData>(context, listen: false).isLoading &&
      (len % 20) == 0 &&
      Provider.of<OrderHistoryData>(context, listen: false).next != null) {
    Provider.of<OrderHistoryData>(context, listen: false)
        .fetchMoreOrderHistory();
  }
}

@override
void dispose() {
  super.dispose();
  _scrollController.dispose();
}

void showFullOrderDetails(ModelOrderHistory order) {
  print('showDialog');
  showDialog(
    context: context,
    builder: (context) {
      print('inside');
      return Dialog(
        insetPadding: const EdgeInsets.all(16),
        shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(31.11)),
        child: const Padding(
          padding:
              EdgeInsets.only(top: 20.0, right: 20, bottom: 50, left: 26),
          child: Text('sdf'),
        ),
      );
    },
  );
}
原因分析

这不是Flutter的bug,核心是滚动监听触发的无限循环+Dialog与宿主Widget的上下文绑定:

  • Dialog弹出时,页面布局发生变化,列表的滚动位置/滚动范围(maxScrollExtent)改变,触发_scrollController的监听回调,执行pagination()。
  • 若分页条件满足,fetchMoreOrderHistory()会修改OrderHistoryData中的状态(如isLoading、allOrders),触发宿主Stateful Widget重建。
  • 由于showDialog的builder函数绑定在宿主Widget的上下文上,宿主重建时builder会被重新执行(打印"inside");同时Dialog的再次创建又会引发布局变化,再次触发滚动监听,形成无限循环。
解决方法

方法1:弹出Dialog时临时移除滚动监听

在弹出Dialog时暂停滚动监听,关闭后恢复,切断循环触发源:

void showFullOrderDetails(ModelOrderHistory order) {
  print('showDialog');
  // 移除滚动监听
  _scrollController.removeListener(pagination);
  showDialog(
    context: context,
    builder: (dialogContext) {
      print('inside');
      return Dialog(
        insetPadding: const EdgeInsets.all(16),
        shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(31.11)),
        child: const Padding(
          padding:
              EdgeInsets.only(top: 20.0, right: 20, bottom: 50, left: 26),
          child: Text('sdf'),
        ),
      );
    },
  ).whenComplete(() {
    // Dialog关闭后恢复监听
    _scrollController.addListener(pagination);
  });
}

方法2:强化分页逻辑的防重复触发

提前判断isLoading状态,避免在请求过程中重复触发分页:

void pagination() {
  final orderData = Provider.of<OrderHistoryData>(context, listen: false);
  // 先判断加载状态,直接拦截重复触发
  if (orderData.isLoading) return;
  
  var len = orderData.allOrders.length;
  if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent &&
      (len % 20) == 0 &&
      orderData.next != null) {
    orderData.fetchMoreOrderHistory();
  }
}

方法3:确保Dialog使用独立上下文

通过builder获取Dialog自身的上下文,避免与宿主Widget的上下文强绑定(此方法需配合前两种使用,单独使用可能无法完全解决循环问题):

void showFullOrderDetails(ModelOrderHistory order) {
  print('showDialog');
  showDialog(
    context: context,
    // 使用Dialog自身的上下文,避免依赖宿主上下文
    builder: (dialogContext) {
      print('inside');
      return Dialog(
        insetPadding: const EdgeInsets.all(16),
        shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(31.11)),
        child: const Padding(
          padding:
              EdgeInsets.only(top: 20.0, right: 20, bottom: 50, left: 26),
          child: Text('sdf'),
        ),
      );
    },
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 03:00:11