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

Flutter中用ModalProgressHUD配合ListView.builder读取Firebase数据时显示加载框

解决Flutter列表页面ModalProgressHUD加载指示器不生效的问题

看起来你遇到的核心问题是**showSpinner变量的状态更新没有触发UI重建**,再加上ListView.builder的渲染时机和Provider状态更新的配合问题,导致加载指示器没按预期工作。咱们一步步来修复:

问题根源分析

  1. showSpinner未通过setState更新:你把showSpinner声明为类级变量,但修改它时没有调用setState((){}),Flutter无法感知状态变化,自然不会刷新UI来隐藏加载指示器。
  2. _refreshList未正确处理异步逻辑:expenseNotifier.getExpenses()应该是异步操作,但你没有在它完成后更新showSpinner的状态。
  3. Consumer内的空状态判断有误:expense是Provider注入的实例,永远不会为null,应该判断expense.expenseList.isEmpty来显示空页面。

修复步骤及完整代码

1. 调整showSpinner的状态更新逻辑

每次触发数据加载时,先通过setState将showSpinner设为true,等异步加载完成后再设为false。

2. 修正_refreshList方法

确保getExpenses是异步方法,并且在它完成后更新状态。

3. 优化空状态判断

把expense == null改为expense.expenseList.isEmpty,让空状态能正确触发。

以下是修正后的完整代码:

bool showSpinner = true;

@override
Widget build(BuildContext context) {
  ExpenseNotifier expenseNotifier = Provider.of<ExpenseNotifier>(context);

  Future<void> _refreshList() async {
    // 开始刷新时显示加载指示器
    setState(() {
      showSpinner = true;
    });
    // 等待数据加载完成
    await expenseNotifier.getExpenses(expenseNotifier);
    // 加载完成后隐藏指示器
    setState(() {
      showSpinner = false;
    });
  }

  // 页面渲染完成后,判断是否需要自动加载数据
  WidgetsBinding.instance.addPostFrameCallback((_) {
    if (expenseNotifier.expenseList.isEmpty) {
      _refreshList();
    } else {
      setState(() {
        showSpinner = false;
      });
    }
  });

  return Scaffold(
    body: ModalProgressHUD(
      inAsyncCall: showSpinner,
      child: RefreshIndicator(
        onRefresh: _refreshList,
        child: Consumer<ExpenseNotifier>(
          builder: (context, expense, child) {
            // 判断列表是否为空,而非expense实例是否为null
            return expense.expenseList.isEmpty 
                ? Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: <Widget>[
                      PaddingClass(bodyImage: 'images/empty.png'),
                      SizedBox(height: 20.0),
                      Text(
                        'You don\'t have any expenses',
                        style: kLabelTextStyle,
                      ),
                    ],
                  )
                : ListView.separated(
                    itemBuilder: (context, int index) {
                      var myExpense = expense.expenseList[index];
                      return Card(
                        elevation: 8.0,
                        color: Colors.white70,
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.start,
                          children: <Widget>[
                            RegularExpenseTextPadding(
                              regText: '${_formattedDate(myExpense.updatedAt)}',
                            ),
                            Container(
                              margin: EdgeInsets.all(20.0),
                              padding: const EdgeInsets.all(15.0),
                              decoration: BoxDecoration(
                                borderRadius: BorderRadius.all(Radius.circular(5.0)),
                                border: Border.all(color: kThemeStyleBorderHighlightColour),
                              ),
                              child: Row(
                                children: <Widget>[
                                  Expanded(
                                    flex: 5,
                                    child: Column(
                                      crossAxisAlignment: CrossAxisAlignment.start,
                                      children: <Widget>[
                                        Text(
                                          '${myExpense.amount}',
                                          style: kRegularTextStyle,
                                        ),
                                        SizedBox(height: 20.0),
                                        Text(
                                          myExpense.description,
                                          style: kRegularTextStyle,
                                        ),
                                      ],
                                    ),
                                  ),
                                  Expanded(
                                    flex: 1,
                                    child: GestureDetector(
                                      onTap: () {
                                        expenseNotifier.currentExpense = expenseNotifier.expenseList[index];
                                        Navigator.of(context).push(
                                          MaterialPageRoute(builder: (BuildContext context) {
                                            return ExpenseDetailsScreen();
                                          }),
                                        );
                                      },
                                      child: Icon(
                                        FontAwesomeIcons.caretDown,
                                        color: kThemeIconColour,
                                      ),
                                    ),
                                  ),
                                ],
                              ),
                            ),
                          ],
                        ),
                      );
                    },
                    separatorBuilder: (BuildContext context, int index) {
                      return SizedBox(height: 20.0);
                    },
                    itemCount: expense.expenseList.length,
                  );
          },
        ),
      ),
    ),
  );
}

额外注意事项

  • 确保ExpenseNotifier.getExpenses()是异步方法,并且在数据加载完成后调用了notifyListeners(),这样Consumer才能感知到expenseList的变化。
  • 页面初始化时的addPostFrameCallback用来避免重复加载数据,也能确保页面渲染完成后再处理状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 09:37:53