Flutter ListView内TextFormField获取输入值出现RangeError报错咨询
Flutter ListView中TextFormField取值报RangeError问题修复
报错根因
- 核心触发原因:
_textFieldRateControllers、_textFieldQtyControllers被定义在build方法内部,每次Widget重建都会被初始化为空列表,此时直接访问列表索引必然触发越界 - 列表操作逻辑顺序错误:在
itemBuilder中先访问_textFieldRateControllers[index],后执行列表add操作,空列表下第一步就会报错 - 属性冲突:
TextFormField同时配置了controller和initialValue,二者互斥,会导致值不生效甚至异常 - 控制器重复创建:每次build都会向列表重复添加控制器,造成内存泄漏、取值错乱
修复步骤
- 将控制器列表移至State类成员变量位置,避免build重建时被重置
- 移除
TextFormField的initialValue属性,初始值直接在初始化控制器时注入 - 拿到产品列表数据后预先初始化所有控制器,长度和产品列表保持一致,避免边渲染边添加的逻辑
- 重写
dispose方法,统一释放所有控制器资源,防止内存泄漏 - 保存按钮取值时遍历所有控制器,不要硬编码固定索引
修正后代码
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
相关产品推荐
相关产品推荐

