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

Flutter:拉取服务端数据时显示进度指示器 解决无限加载问题

问题根因

无限加载是FutureBuilder错误用法搭配setState触发重建形成死循环导致的,执行链路如下:

  • 直接在FutureBuilder的future参数位置调用getQoutes(),每次build方法执行时,都会重新触发一次getQoutes异步请求
  • getQoutes请求完成后会调用setState(),而setState会强制当前页面重新执行build方法
  • 上述逻辑无限循环,FutureBuilder永远在等待新的Future执行完成,始终停留在加载态,加载指示器自然不会消失
修复方案

二选一即可,不要混用两种逻辑。

方案1:遵循FutureBuilder设计规范,移除多余setState

FutureBuilder本身会监听传入的Future状态自动刷新UI,不需要额外调用setState存数据,核心修改点:

  • 不要在异步请求方法里调用setState,直接将请求到的数据作为返回值返回
  • 不要在build方法内直接执行异步方法赋值给future,要在initState里提前初始化、存储Future实例,避免重建时重复触发请求
  • 直接从AsyncSnapshot中取数据渲染UI

修改后的核心代码:

late Future<Map<String, dynamic>> _quotesFuture;

Future<Map<String, dynamic>> getQoutes() async {
  final data = await FirebaseFirestore.instance
      .collection('user')
      .doc(auth.currentUser!.uid)
      .get();
  return data.data() as Map<String, dynamic>;
}

@override
void initState() {
  super.initState();
  _quotesFuture = getQoutes();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('Some Quotes'),
      backgroundColor: Colors.deepOrange,
    ),
    body: FutureBuilder<Map<String, dynamic>>(
      future: _quotesFuture,
      builder: (context, snapshot) {
        if (snapshot.connectionState != ConnectionState.done) {
          return const Center(child: CircularProgressIndicator());
        }
        if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        }
        final userInfo = snapshot.data!;
        return SingleChildScrollView(
          child: Container(
            height: MediaQuery.of(context).size.height,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Expanded(
                  child: ListView.builder(
                    itemCount: 3,
                    scrollDirection: Axis.vertical,
                    itemBuilder: (context, index) {
                      return Card_fun(userInfo['quotes'][index]);
                    },
                  ),
                )
              ],
            ),
          ),
        );
      },
    ),
  );
}

方案2:放弃FutureBuilder,手动管理加载状态

如果习惯用setState管理页面状态,就完全移除FutureBuilder,自行维护加载状态标记:

Map<String, dynamic> userInfo = {};
bool _isLoading = true;
dynamic _loadError;

Future<void> getQoutes() async {
  try {
    final data = await FirebaseFirestore.instance
        .collection('user')
        .doc(auth.currentUser!.uid)
        .get();
    setState(() {
      userInfo = data.data() as Map<String, dynamic>;
      _isLoading = false;
    });
  } catch (e) {
    setState(() {
      _loadError = e;
      _isLoading = false;
    });
  }
}

@override
void initState() {
  super.initState();
  getQoutes();
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('Some Quotes'),
      backgroundColor: Colors.deepOrange,
    ),
    body: _isLoading
        ? const Center(child: CircularProgressIndicator())
        : _loadError != null
            ? Text('Error: $_loadError')
            : SingleChildScrollView(
                child: Container(
                  height: MediaQuery.of(context).size.height,
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      Expanded(
                        child: ListView.builder(
                          itemCount: 3,
                          scrollDirection: Axis.vertical,
                          itemBuilder: (context, index) {
                            return Card_fun(userInfo['quotes'][index]);
                          },
                        ),
                      )
                    ],
                  ),
                ),
              ),
  );
}
避坑提醒
  • 禁止直接在FutureBuilder的future参数位置执行返回Future的方法,否则只要触发build(键盘弹出、屏幕旋转、父组件刷新、setState调用),异步方法就会重复执行,轻则浪费请求资源,重则触发死循环
  • FutureBuilder本身已经接管了异步状态的UI更新逻辑,不要在其传入的Future方法里额外调用setState,避免重复触发重建

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 03:51:23