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

Flutter ListView内TextFormField获取输入值出现RangeError报错咨询

Flutter ListView中TextFormField取值报RangeError问题修复

报错根因

  • 核心触发原因:_textFieldRateControllers、_textFieldQtyControllers 被定义在build方法内部,每次Widget重建都会被初始化为空列表,此时直接访问列表索引必然触发越界
  • 列表操作逻辑顺序错误:在itemBuilder中先访问_textFieldRateControllers[index],后执行列表add操作,空列表下第一步就会报错
  • 属性冲突:TextFormField同时配置了controller和initialValue,二者互斥,会导致值不生效甚至异常
  • 控制器重复创建:每次build都会向列表重复添加控制器,造成内存泄漏、取值错乱

修复步骤

  1. 将控制器列表移至State类成员变量位置,避免build重建时被重置
  2. 移除TextFormField的initialValue属性,初始值直接在初始化控制器时注入
  3. 拿到产品列表数据后预先初始化所有控制器,长度和产品列表保持一致,避免边渲染边添加的逻辑
  4. 重写dispose方法,统一释放所有控制器资源,防止内存泄漏
  5. 保存按钮取值时遍历所有控制器,不要硬编码固定索引

修正后代码

class _OrderBookingState extends State<OrderBooking> {
  // 控制器列表声明为类成员,避免build重建时被重置
  final List<TextEditingController> _textFieldRateControllers = [];
  final List<TextEditingController> _textFieldQtyControllers = [];

  @override
  void dispose() {
    // 统一释放所有控制器资源
    for (var controller in _textFieldRateControllers) {
      controller.dispose();
    }
    for (var controller in _textFieldQtyControllers) {
      controller.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Center(child: Text(widget.shop.shopName)),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: <Widget>[
          Container(
            child: StreamBuilder<List<ProductModel>>(
              stream: DatabaseService().productList,
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  // 先清空并释放旧控制器,避免产品列表变动后残留
                  for (var c in _textFieldRateControllers) c.dispose();
                  for (var c in _textFieldQtyControllers) c.dispose();
                  _textFieldRateControllers.clear();
                  _textFieldQtyControllers.clear();

                  // 预初始化所有控制器,初始值直接注入
                  for (var product in snapshot.data) {
                    final rateController = TextEditingController(text: product.rate);
                    final qtyController = TextEditingController(text: "0");
                    _textFieldRateControllers.add(rateController);
                    _textFieldQtyControllers.add(qtyController);
                  }

                  return Flexible(
                    child: ListView.builder(
                      scrollDirection: Axis.vertical,
                      shrinkWrap: true,
                      itemCount: snapshot.data.length,
                      itemBuilder: (context, index) {
                        return Padding(
                          padding: EdgeInsets.only(top: 8.0),
                          child: Card(
                            margin: EdgeInsets.fromLTRB(10.0, 6.0, 10.0, 0.0),
                            child: ListTile(
                              leading: const CircleAvatar(
                                radius: 25.0,
                                backgroundColor: Colors.deepOrangeAccent,
                              ),
                              title: Row(
                                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                                children: <Widget>[
                                  Container(
                                    width: 175,
                                    child: Text(snapshot.data[index].productName),
                                  ),
                                  const SizedBox(width: 5),
                                  Expanded(
                                    child: Container(
                                      width: 10,
                                      child: TextFormField(
                                        controller: _textFieldRateControllers[index],
                                        textAlign: TextAlign.center,
                                      ),
                                    ),
                                  ),
                                  const SizedBox(width: 5),
                                  Expanded(
                                    child: TextFormField(
                                      controller: _textFieldQtyControllers[index],
                                      textAlign: TextAlign.center,
                                    ),
                                  )
                                ],
                              ),
                              subtitle: Text('MRP ' + snapshot.data[index].mrp),
                            ),
                          ),
                        );
                      },
                    ),
                  );
                } else {
                  return const Loading();
                }
              },
            ),
          ),
          Container(
            margin: const EdgeInsets.fromLTRB(10.0, 6.0, 10.0, 0.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                Text(
                  "Total Value: 0/-",
                  style: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.bold,
                    backgroundColor: Colors.greenAccent,
                  ),
                ),
                RaisedButton(
                  child: const Text(
                    "Save",
                    style: TextStyle(fontSize: 18.0),
                  ),
                  color: Colors.green,
                  textColor: Colors.white,
                  highlightColor: Colors.lightGreenAccent,
                  onPressed: () {
                    // 遍历所有控制器获取输入值
                    for (int i = 0; i < _textFieldQtyControllers.length; i++) {
                      print("第${i+1}个商品单价:${_textFieldRateControllers[i].text}");
                      print("第${i+1}个商品数量:${_textFieldQtyControllers[i].text}");
                    }
                  },
                )
              ],
            ),
          )
        ],
      ),
    );
  }
}

注意:StreamBuilder的builder会在流数据更新时反复执行,预初始化控制器的逻辑放在snapshot.hasData判断后,避免空数据时触发异常。如果产品列表频繁更新,可搭配状态管理工具更优雅地管理控制器,当前方案为适配现有代码逻辑的最小改动修复。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.01 05:54:03