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
相关产品推荐
相关产品推荐

