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

Flutter中Range error(index)问题:Firestore数据加载致复选框列表异常

问题描述

我有两个列表:第一个是从Firestore获取数据的PlanExerciseslist,第二个是复选框状态列表isChecked。初始化代码如下:

late List<bool> isChecked;
isChecked = List<bool>.filled(LifeStyleCubit.get(context).PlanExerciseslist.length, false);

问题在于,PlanExerciseslist从Firestore获取数据需要时间,初始化isChecked时其长度为0,因此触发Range error(index)。完整代码如下:

class BackPlanScreen extends StatefulWidget {
  PlanQsModel model;
  int dayNum;
  int weekNum;
  BackPlanScreen(this.model, this.dayNum, this.weekNum);

  @override
  State<BackPlanScreen> createState() => _BackPlanScreenState();
}

class _BackPlanScreenState extends State<BackPlanScreen> {
  late List<bool> isChecked;
  @override
  void initState() {
    super.initState();
    isChecked = List<bool>.filled(LifeStyleCubit.get(context).PlanExerciseslist.length, false);
  }

  @override
  Widget build(BuildContext context) {
    return BlocConsumer<LifeStyleCubit, LifeStates>(
      listener: (context, state)
      {},
      builder: (context, state)
      {
        var cubit = LifeStyleCubit.get(context);
        cubit.getPlanBackEx1(widget.model.level);
        return Scaffold(
          appBar: AppBar(
            title: Text('Exercise'),
          ),
          body: Padding(
            padding: const EdgeInsets.all(20.0),
            child: ListView.separated(
                scrollDirection: Axis.vertical,
                itemBuilder:(context, index) => BackWidget(cubit.PlanExerciseslist[index], context, index),
                separatorBuilder: (context, index) => SizedBox(height: 35,),
                itemCount: cubit.PlanExerciseslist.length
            ),
          ),
        );
      },
    );
  }

  Widget BackWidget (PlanExercisesModel model, context, index) => GestureDetector(
    onTap: () => Navigator.push(context,
        MaterialPageRoute(builder: (context) => BackPlanGifScreen(model))),
    child: Builder(
      builder: (context) {
        return Container(
          height: 100,
          width: MediaQuery.of(context).size.width,
          child: SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: Row(
              children: [
                Checkbox(
                    value: isChecked[index],
                    activeColor: Colors.orangeAccent,
                    onChanged: (newBool) {
                      setState(() {
                        isChecked[index] = newBool!;
                        if (isChecked[index] == true)
                        {
                          LifeStyleCubit.get(context).createPlanExercisesDay(
                            name: model.name,
                            difficulty: model.difficulty,
                            bodyPart: model.bodyPart,
                            equipment: model.equipment,
                            gifURL: model.gifURL,
                            target: model.target,
                            weekNum: widget.weekNum + 1,
                            dayNum: widget.dayNum + 1,
                            id: widget.model.description,
                          );
                        }
                        if(isChecked[index] == false) {
                          LifeStyleCubit.get(context).deletePlanExercisesDay(
                              weekNum: widget.weekNum + 1,
                              dayNum: widget.dayNum + 1,
                              id: widget.model.description,
                              name: model.name
                          );
                        }
                      });
                    }
                ),
              ],
            ),
          ),
        );
      }
    ),
  );
}

请问如何解决该问题?或者如何让复选框列表等待数据加载完成?


解决方案

1. 延迟初始化isChecked到数据加载完成时

initState执行时Firestore数据还未返回,此时初始化isChecked没有意义。可以通过Bloc的状态监听,在数据加载完成后再更新isChecked:

class _BackPlanScreenState extends State<BackPlanScreen> {
  late List<bool> isChecked;

  @override
  void initState() {
    super.initState();
    isChecked = [];
    // 只在初始化时发起一次数据请求
    LifeStyleCubit.get(context).getPlanBackEx1(widget.model.level);
  }

  @override
  Widget build(BuildContext context) {
    return BlocConsumer<LifeStyleCubit, LifeStates>(
      listener: (context, state) {
        // 监听数据加载完成的状态(需对应你的Cubit中定义的成功状态,比如PlanExercisesLoaded)
        if (state is PlanExercisesLoaded) {
          setState(() {
            // 数据就绪后,初始化对应长度的复选框状态列表
            isChecked = List<bool>.filled(LifeStyleCubit.get(context).PlanExerciseslist.length, false);
          });
        }
      },
      builder: (context, state) {
        var cubit = LifeStyleCubit.get(context);

        // 数据未加载完成时显示加载状态
        if (cubit.PlanExerciseslist.isEmpty) {
          return Scaffold(
            appBar: AppBar(title: Text('Exercise')),
            body: Center(child: CircularProgressIndicator()),
          );
        }

        return Scaffold(
          appBar: AppBar(title: Text('Exercise')),
          body: Padding(
            padding: const EdgeInsets.all(20.0),
            child: ListView.separated(
                scrollDirection: Axis.vertical,
                itemBuilder:(context, index) => BackWidget(cubit.PlanExerciseslist[index], context, index),
                separatorBuilder: (context, index) => SizedBox(height: 35),
                itemCount: cubit.PlanExerciseslist.length
            ),
          ),
        );
      },
    );
  }

2. 增加复选框取值的安全判断

避免因列表长度不一致导致的索引越界问题:

Widget BackWidget (PlanExercisesModel model, context, index) => GestureDetector(
  onTap: () => Navigator.push(context,
      MaterialPageRoute(builder: (context) => BackPlanGifScreen(model))),
  child: Builder(
    builder: (context) {
      // 安全判断,索引超出范围时返回空容器
      if (index >= isChecked.length) return Container();
      
      return Container(
        height: 100,
        width: MediaQuery.of(context).size.width,
        child: SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: Row(
            children: [
              Checkbox(
                  value: isChecked[index],
                  activeColor: Colors.orangeAccent,
                  onChanged: (newBool) {
                    setState(() {
                      isChecked[index] = newBool!;
                      if (isChecked[index]) {
                        LifeStyleCubit.get(context).createPlanExercisesDay(
                          name: model.name,
                          difficulty: model.difficulty,
                          bodyPart: model.bodyPart,
                          equipment: model.equipment,
                          gifURL: model.gifURL,
                          target: model.target,
                          weekNum: widget.weekNum + 1,
                          dayNum: widget.dayNum + 1,
                          id: widget.model.description,
                        );
                      } else {
                        LifeStyleCubit.get(context).deletePlanExercisesDay(
                            weekNum: widget.weekNum + 1,
                            dayNum: widget.dayNum + 1,
                            id: widget.model.description,
                            name: model.name
                        );
                      }
                    });
                  }
              ),
            ],
          ),
        ),
      );
    }
  ),
);

3. 额外优化建议

  • 不要在build方法中重复调用cubit.getPlanBackEx1,否则每次UI重建都会发起请求,改为在initState中调用一次。
  • 为Bloc定义明确的状态枚举(如PlanExercisesLoading、PlanExercisesLoaded、PlanExercisesError),可以更精准地处理加载中、加载成功、加载失败的不同UI状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 04:53:29