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

Flutter拉取Firestore数据后无法渲染至Widget的问题求助

问题根因

你的代码存在3个核心问题,直接导致列表无法正常渲染:

  • FutureBuilder的builder回调是同步执行的,你在ConnectionState.done分支里调用getExerciseList()属于嵌套异步操作,.then()回调中返回的ListView不会被FutureBuilder识别渲染。执行完done分支的同步代码后,会直接走到方法末尾默认返回的TextButton组件,因此页面永远显示按钮。
  • 你把Firestore请求直接写在了build方法的future参数里,每次setState触发组件重建时,都会重新发起一遍请求,陷入「重建→发起请求→短暂显示加载态→请求完成→返回默认组件」的循环,这也是你点击按钮只短暂显示加载圈的原因。
  • 没有增加请求异常的判断分支,一旦Firestore请求报错,也会直接走到默认返回逻辑,看不到错误提示。
修复方案
  1. 不要在build方法里直接初始化Future请求,将完整的请求逻辑提前到initState中初始化,避免组件重建时重复发起无效请求
  2. 合并两个嵌套的Firestore请求为一个完整的异步任务,统一交给FutureBuilder等待结果,不要在builder回调内单独开异步逻辑
  3. 为FutureBuilder指定明确的泛型类型,增加错误、空数据的分支判断,避免隐式类型错误和无反馈的异常场景

修复后的完整代码参考:

class _MyExercisesListState extends State<MyExercisesList> {
  final MyWorkoutData workout;
  // 提前定义承载完整请求逻辑的Future,避免build时重复创建
  late Future<List<MyExerciseData>> exercisesFuture;

  _MyExercisesListState(this.workout);

  @override
  void initState() {
    super.initState();
    // 初始化阶段就发起请求,不会因setState重复触发
    exercisesFuture = _fetchExercises();
  }

  // 合并两个嵌套的Firestore请求为一个完整异步任务
  Future<List<MyExerciseData>> _fetchExercises() async {
    final uid = FirebaseAuth.instance.currentUser!.uid;
    final dbService = FirestoreDatabaseService(uid: uid);
    // 先获取训练计划信息
    final workoutSnapshot = await dbService.getWorkout(workout.title);
    final workoutId = workoutSnapshot.docs[0]['id'].toString();
    // 再获取关联的练习列表
    final exerciseSnapshot = await dbService.getExerciseList(workoutId);
    // 解析数据为模型对象
    List<MyExerciseData> exercises = [];
    for (final ele in exerciseSnapshot.docs) {
      List<String> steps = ele.get('steps').cast<String>();
      exercises.add(MyExerciseData(
        description: ele.get('description').toString(),
        minutes: ele.get('minutes'),
        progress: ele.get('progress'),
        steps: steps,
        title: ele.get('title').toString(),
        video: ele.get('video').toString(),
      ));
    }
    return exercises;
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<List<MyExerciseData>>(
      future: exercisesFuture,
      builder: (BuildContext context, AsyncSnapshot<List<MyExerciseData>> snapshot) {
        // 优先处理错误场景
        if (snapshot.hasError) {
          return Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text("加载失败:${snapshot.error}"),
            ],
          );
        }
        // 按连接状态返回对应组件
        switch (snapshot.connectionState) {
          case ConnectionState.none:
            return Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [Text("Lỗi kết nối")],
            );
          case ConnectionState.waiting:
            return Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [CircularProgressIndicator()],
            );
          case ConnectionState.active:
          case ConnectionState.done:
            final exercises = snapshot.data ?? [];
            if (exercises.isEmpty) {
              return Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [Text("暂无练习数据")],
              );
            }
            return ListView.separated(
              padding: EdgeInsets.only(top: 10),
              itemCount: exercises.length,
              itemBuilder: (context, index) {
                return MyExerciseCell(
                  exercise: exercises[index],
                  workout: workout,
                );
              },
              separatorBuilder: (context, index) {
                return const SizedBox(height: 15);
              },
            );
        }
      },
    );
  }
}
额外说明

如果后续需要手动触发列表刷新,不要直接调用setState,而是给exercisesFuture重新赋值新的请求任务后再调用setState即可,避免出现重复请求的问题。

内容的提问来源于stack exchange,提问作者Dang Duong Le

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 13:30:45