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

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组件上,提交前也没有做表单校验。
修复方案

按以下步骤调整即可解决问题:

  1. 删掉所有重复声明的控制器列表,只在State类中保留唯一一份答案控制器列表,不要在build方法、子函数中重新声明同名的局部列表变量。
  2. 把FutureBuilder用到的接口请求提前到initState中初始化,避免build阶段重复触发请求。
  3. 等接口返回题目数据后,再根据题目实际数量一次性初始化对应数量的控制器,不要在数据未就绪时提前初始化。
  4. 把控制器列表作为参数传入buildBodyContent,保证输入框绑定的控制器和后续提交读取的控制器是同一个实例,禁止在ListView的itemBuilder中重复add控制器。
  5. 在State的dispose生命周期中遍历控制器列表,逐个释放资源。
  6. 把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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 05:36:25