Flutter TextFormField绑定TextEditingController列表报RangeError
问题根因
报错和取值异常是几个逻辑错误叠加导致的:
- 代码里重复定义了至少4组互相独立的
TextEditingController列表:State类里的_controllers、controllers,buildBodyContent方法内部的局部controllers、_controllers,这几个列表内存地址完全不同,你提交时读取的列表,和实际绑定到TextFormField的列表根本不是同一个。 initState阶段执行show()初始化控制器时,无法保证GlobalParams.quizmodel.quizQuestions已经完成数据加载,此时如果列表为空,初始化出来的控制器列表长度就是0,直接访问索引0必然抛出范围错误。buildBodyContent是独立于State的顶层函数,它内部声明的controllers是局部变量,每次页面重绘都会重新创建为空列表,ListView构建item时还会重复往列表里追加控制器,不仅外部State完全访问不到这部分控制器实例,还会造成控制器重复创建、内存泄漏。- 接口请求的Future直接写在
build方法里,每次页面重绘都会重新发起请求,导致列表状态反复重置。 - 所有
TextEditingController都没有在生命周期结束时调用dispose()释放,存在内存泄漏风险。 - 你已经定义了
globalFormKey但根本没有绑定到Form组件上,提交前也没有做表单校验。
修复方案
按以下步骤调整即可解决问题:
- 删掉所有重复声明的控制器列表,只在State类中保留唯一一份答案控制器列表,不要在build方法、子函数中重新声明同名的局部列表变量。
- 把FutureBuilder用到的接口请求提前到
initState中初始化,避免build阶段重复触发请求。 - 等接口返回题目数据后,再根据题目实际数量一次性初始化对应数量的控制器,不要在数据未就绪时提前初始化。
- 把控制器列表作为参数传入
buildBodyContent,保证输入框绑定的控制器和后续提交读取的控制器是同一个实例,禁止在ListView的itemBuilder中重复add控制器。 - 在State的
dispose生命周期中遍历控制器列表,逐个释放资源。 - 把
globalFormKey绑定到Form组件上,提交时先校验表单状态再读取值。
核心修正代码
State类基础配置
class _AnswerQuizPageState extends State<AnswerQuizPage> { int? _numPages ; int _currentPage = 1; bool test=false; bool colorchoice=false; var questionController= QuestionController(QuestionRepositoryImpl()); List<String> reponse=[]; List<String> asnwers=[]; // 只保留这唯一一份控制器列表,删掉其他所有重复声明的控制器列表 List<TextEditingController> answerControllers = []; List<QuestionModel> listofquestion=[]; List<QuizQuestion> listofquizquestionsforquiz=GlobalParams.quizmodel.quizQuestions; List<dynamic> trList=[]; GlobalKey<FormState> globalFormKey=GlobalKey<FormState>(); // 提前初始化future,避免build重复请求 late Future<List<QuestionModel>> quizFuture; @override void initState(){ super.initState(); quizFuture = questionController.getQuestionsByQuizId(GlobalParams.quizmodel.id); } @override void dispose() { // 释放所有控制器,避免内存泄漏 for (var controller in answerControllers) { controller.dispose(); } super.dispose(); } // 把buildBodyContent挪到State内部,或者接收控制器列表作为参数 SafeArea buildBodyContent(AsyncSnapshot<List<QuestionModel>> snapshot) { bool colorchoice=false; Color colorDet; // 删掉这里所有重新声明的局部控制器、列表变量,直接使用State中的answerControllers return SafeArea( child: Scrollbar( isAlwaysShown: true, thickness: 5, child: ListView.builder( itemBuilder: (context,index) { var cq=snapshot.data?[index]; // 删掉这里重复add控制器的逻辑,控制器统一在数据加载完成后初始化 return Column( children:[ SizedBox(height: 10,), Container( width: MediaQuery.of(context).size.width/2, padding: EdgeInsets.only(top:5.0, bottom: 5.0, left: 10.0, right: 10.0), decoration: BoxDecoration( color: Colors.orange, borderRadius: BorderRadius.all(Radius.circular(10)), ), child: Column( children: [ SizedBox(height: 10,), Text(cq!.enonce, style: TextStyle(fontSize: 16),), SizedBox(height: 10,), Text('réponse a : ${cq.propositionA}', style: TextStyle(fontSize: 16),), SizedBox(height: 10,), Text('réponse b : ${cq.propositionB}', style: TextStyle(fontSize: 16),), SizedBox(height: 10,), Text('réponse c : ${cq.propositionC}', style: TextStyle(fontSize: 16),), SizedBox(height: 10,), Container( child: InkWell( onTap: (){ colorchoice=!colorchoice; colorDet = colorchoice ? Colors.green : Colors.orange; }, child: Text('réponse d : ${cq.propositionD}', style: TextStyle(fontSize: 16),), ), ), SizedBox(height: 10,), ], ), ), SizedBox(height: 10,), Column( children: [ TextFormField( // 直接绑定唯一的控制器列表 controller: answerControllers[index], decoration: InputDecoration(hintText: ' réponse'), validator: (value){ if (value!.isNotEmpty){ return null; } else { return "libelle ne peut pas être vide"; } }, keyboardType: TextInputType.text, ), ], ), SizedBox(height: 10,), ], ); }, itemCount: snapshot.data?.length ??0 ), ), ); }
FutureBuilder逻辑调整
FutureBuilder<List<QuestionModel>>( future: quizFuture, // 使用initState中初始化的future builder:(context ,snapshot){ if(snapshot.connectionState==ConnectionState.waiting){ return Center(child: CircularProgressIndicator(),); } if (snapshot.hasError){ print('${snapshot.error}'); print('${snapshot}'); return Center(child: Text('${snapshot}'),); } // 数据加载完成后,统一初始化控制器 listofquestion = snapshot.data!; _numPages = listofquestion.length; answerControllers.clear(); for (var i = 0; i < listofquestion.length; i++) { answerControllers.add(TextEditingController()); } return Form( // 绑定表单key key: globalFormKey, child: Column( children: [ Expanded(child: buildBodyContent(snapshot)), SizedBox(height: 50,), Container( child: FormHelper.submitButton("Ajouter", () async{ // 先校验表单 if (!globalFormKey.currentState!.validate()) { return; } print('hello'); print('${GlobalParams.quizmodel.id}'); print('${GlobalParams.quizmodel.quizQuestions[0].quizquestionpk.idQuestion}'); // 直接读取唯一的控制器列表取值 for (var j=0;j<answerControllers.length;j++){ print('第${j+1}题答案:${answerControllers[j].text}'); } }, btnColor: Colors.blue, borderColor: Colors.white, txtColor: Colors.white, borderRadius: 10,), ), ], ), ); }, )
调整后不会再出现范围报错,提交时可以正常拿到所有输入框的用户输入内容。
内容的提问来源于stack exchange,提问作者Houssem
相关产品推荐
相关产品推荐

