Flutter RangeError(index)问题排查:非ListView.Builder场景索引越界
解决RangeError (index): Invalid value: Range is empty错误
问题描述
应用运行正常,但会抛出RangeError (index): Invalid value: Range is empty错误。使用FutureBuilder从数据库读取数据,默认展示列表第一条,通过初始值为0的计数器切换数据。怀疑是空列表访问索引导致,但网上方案多针对ListView.Builder,未找到适配场景的解决办法。
相关代码
// 计数器变量声明 var index = 0; // 数据库读取函数 Future<List?> read(String query) async { var result = await SqlConn.readData(query); query.replaceAll("\"", ""); List _list = jsonDecode(result) as List; debugPrint('${_list.length} <===== size'); return _list; } // 数据展示部分代码 child: FutureBuilder<List?>( future: read( "SELECT ProductAdress, replace(ProductName, '\"', '')ProductName, NeedCount, ScanCount, ProductBarCode, ProductSeriesCount, ProductExpirationDate FROM ScanRest WHERE ProductStation = '${widget.nrStatie}' AND BoxID = '$cutieScan' Order By ProductName ASC"), builder: (context, snapshot) { switch (snapshot.connectionState) { case ConnectionState.waiting: return const Text('Loading....'); default: if (snapshot.hasError) { debugPrint( "call error"); return Text('Error: ${snapshot.error}'); } else { debugPrint( "call success"); List data = snapshot.data ?? []; return Column(children: [ Row( children: [ // 产品地址 Expanded( child: GestureDetector( onTap: () { setState(() { i++; if (i == snapshot.data!.length) { i = 0; } }); }, child: SizedBox( height: 60, child: Center( child: Text( 'i=' +i.toString() + " " + ((data[i] as Map)['ProductAdress'].toString()), style: const TextStyle(fontSize: 30), ), )), )), // 需求数量 Expanded( child: GestureDetector( onTap: () { _nrProdusController.text = (data[i] as Map)['NeedCount'].toString(); }, child: SizedBox( height: 60, child: Center( child: Text( ((data[i] as Map)['NeedCount'] .toString()), style: TextStyle(fontSize: 35, fontWeight: FontWeight.bold, color: Colors.primaries[Random().nextInt(Colors.primaries.length)]), ), ), ), )), ], ),
问题根源
- 空列表直接访问索引:当数据库查询无结果时,
data = snapshot.data ?? []会变成空列表,但代码直接使用data[i](i初始为0),触发空列表的RangeError。 - 计数器逻辑隐患:切换时用
snapshot.data!.length强制解包,若snapshot.data为空会触发空指针;同时计数器变量index和i不一致,易引发状态混乱。 - FutureBuilder重复请求:每次组件重建都会调用
read()重新查询,既浪费资源也可能导致数据状态不一致。
修复方案
1. 新增空列表判断
在获取数据后先检查列表是否为空,避免访问无效索引:
List data = snapshot.data ?? []; if (data.isEmpty) { return const Center(child: Text('暂无数据')); }
2. 修复计数器逻辑
- 统一计数器变量名,避免混淆;
- 用
data.length代替snapshot.data!.length,避免强制解包; - 确保计数器不越界:
// 统一计数器变量 int currentIndex = 0; // 点击切换逻辑 setState(() { currentIndex++; if (currentIndex == data.length) { currentIndex = 0; } }); // 额外防护:确保currentIndex在有效范围内 if (currentIndex >= data.length) { currentIndex = 0; }
3. 优化FutureBuilder请求
在initState中提前初始化查询Future,避免组件重建时重复请求:
late Future<List?> _fetchDataFuture; @override void initState() { super.initState(); _fetchDataFuture = read( "SELECT ProductAdress, replace(ProductName, '\"', '')ProductName, NeedCount, ScanCount, ProductBarCode, ProductSeriesCount, ProductExpirationDate FROM ScanRest WHERE ProductStation = '${widget.nrStatie}' AND BoxID = '$cutieScan' Order By ProductName ASC" ); } // FutureBuilder中使用初始化好的future child: FutureBuilder<List?>( future: _fetchDataFuture, // ... 其他代码 )
4. 修正无效代码
原代码中query.replaceAll("\"", "")无实际作用,replaceAll返回新字符串,原变量不会修改。若需处理结果中的引号,应在jsonDecode前处理result:
// 修改read函数 Future<List?> read(String query) async { var result = await SqlConn.readData(query); // 处理结果中的引号 result = result.replaceAll("\"", ""); List _list = jsonDecode(result) as List; debugPrint('${_list.length} <===== size'); return _list; }
完整修复后代码片段
int currentIndex = 0; late Future<List?> _fetchDataFuture; @override void initState() { super.initState(); _fetchDataFuture = read( "SELECT ProductAdress, replace(ProductName, '\"', '')ProductName, NeedCount, ScanCount, ProductBarCode, ProductSeriesCount, ProductExpirationDate FROM ScanRest WHERE ProductStation = '${widget.nrStatie}' AND BoxID = '$cutieScan' Order By ProductName ASC" ); } Future<List?> read(String query) async { var result = await SqlConn.readData(query); result = result.replaceAll("\"", ""); List _list = jsonDecode(result) as List; debugPrint('${_list.length} <===== size'); return _list; } // 数据展示部分 child: FutureBuilder<List?>( future: _fetchDataFuture, builder: (context, snapshot) { switch (snapshot.connectionState) { case ConnectionState.waiting: return const Text('加载中....'); default: if (snapshot.hasError) { debugPrint("请求错误"); return Text('错误: ${snapshot.error}'); } else { debugPrint("请求成功"); List data = snapshot.data ?? []; if (data.isEmpty) { return const Center(child: Text('暂无数据')); } if (currentIndex >= data.length) { currentIndex = 0; } return Column(children: [ Row( children: [ Expanded( child: GestureDetector( onTap: () { setState(() { currentIndex++; if (currentIndex == data.length) { currentIndex = 0; } }); }, child: SizedBox( height: 60, child: Center( child: Text( 'currentIndex=${currentIndex} ${(data[currentIndex] as Map)['ProductAdress'].toString()}', style: const TextStyle(fontSize: 30), ), )), )), Expanded( child: GestureDetector( onTap: () { _nrProdusController.text = (data[currentIndex] as Map)['NeedCount'].toString(); }, child: SizedBox( height: 60, child: Center( child: Text( (data[currentIndex] as Map)['NeedCount'].toString(), style: TextStyle( fontSize: 35, fontWeight: FontWeight.bold, color: Colors.primaries[Random().nextInt(Colors.primaries.length)]), ), ), ), )), ], ), // 其他组件... ]); } } }, ),
内容的提问来源于stack exchange,提问作者Dima C
相关产品推荐
相关产品推荐

