Flutter DataTable结合FutureBuilder红屏列数不匹配报错修复
报错根因
抛出的Failed assertion: '!rows.any((DataRow row) => row.cells.length != columns.length)'断言触发逻辑非常明确:DataTable 组件强制要求每一行的 DataCell 数量,必须和表头 DataColumn 的数量完全相等,只要任意一行单元格数多了或者少了,就会直接抛红屏异常。
结合用 FutureBuilder 异步加载List<MaintenanceDetailsByDriverNo>生成 rows 的场景,常见触发点共四类:
- 异步加载的不同状态分支(加载中/报错/空数据/加载完成)返回的行单元格数和表头列数不匹配,最常见的是加载中只放了一个加载动画占1个单元格,和多列的表头对不上
- 遍历业务数据生成 DataRow 时,加了字段判空的条件渲染,某条数据字段为空时直接跳过了对应单元格的生成,导致这一行单元格数少了
- 调整表头列数的时候,忘了同步修改行单元格的生成逻辑,两边数量不对等
- 业务列表里存在脏数据,某条
MaintenanceDetailsByDriverNo对象字段缺失,导致条件渲染时少生成了单元格
修复步骤
1. 先抽离固定列数,从根源避免硬编码不一致
不要分别在columns和rows的代码里硬编码列数,抽成统一常量,两边都用这个值做生成基准:
// 统一列数常量,所有行、列生成都以这个值为准 const int _tableColumnNum = 5; List<DataColumn> _generateColumns() { return List.generate(_tableColumnNum, (colIndex) { switch (colIndex) { case 0: return const DataColumn(label: Text('维保单号')); case 1: return const DataColumn(label: Text('绑定车牌')); case 2: return const DataColumn(label: Text('维保日期')); case 3: return const DataColumn(label: Text('维保费用')); case 4: return const DataColumn(label: Text('操作')); default: return const DataColumn(label: SizedBox.shrink()); } }); }
2. 统一所有异步状态的行生成逻辑
FutureBuilder 的所有状态分支(等待/报错/空数据/正常加载),返回的每一行 DataRow 的 cells 长度必须严格等于_tableColumnNum,举个典型错误和对应修正:
❌ 错误写法:加载态行只有1个单元格,和5列表头不匹配
if (snapshot.connectionState == ConnectionState.waiting) { return DataTable( columns: _generateColumns(), rows: [ // 这里cells长度为1,直接触发断言 DataRow(cells: [const DataCell(Center(child: CircularProgressIndicator()))]) ], ); }
✅ 正确写法:占位行单元格数和表头完全对齐
if (snapshot.connectionState == ConnectionState.waiting) { return DataTable( columns: _generateColumns(), rows: [ DataRow( cells: List.generate(_tableColumnNum, (colIndex) { // 加载提示放在第一列,其余列留空占位 if (colIndex == 0) { return const DataCell(Center(child: CircularProgressIndicator())); } return const DataCell(SizedBox.shrink()); }), ) ], ); }
空数据、加载报错的占位行也按这个逻辑写,不要只放1个占满整行的单元格——DataTable 本身不支持单元格跨列,硬写就会触发数量不匹配的断言。
3. 业务数据行用统一规则生成
遍历List<MaintenanceDetailsByDriverNo>生成正式数据行时,同样用List.generate按固定列数生成,不要手动逐个写DataCell,避免漏写:
List<DataRow> _generateDataRows(List<MaintenanceDetailsByDriverNo> sourceList) { return sourceList.map((item) { return DataRow( cells: List.generate(_tableColumnNum, (colIndex) { switch (colIndex) { case 0: return DataCell(Text(item.maintainOrderNo ?? '-')); case 1: return DataCell(Text(item.plateNumber ?? '-')); case 2: return DataCell(Text(item.maintainDate ?? '-')); case 3: return DataCell(Text('${item.cost ?? 0} 元')); case 4: return DataCell(TextButton(onPressed: (){}, child: const Text('查看详情'))); default: return const DataCell(SizedBox.shrink()); } }), ); }).toList(); }
所有字段判空给默认值,不要因为字段为空就跳过单元格生成。
快速定位异常行技巧
如果改完还是报错,不用逐行翻代码找哪行数量不对,在把rows传给DataTable之前加一段校验代码,控制台会直接打印异常行的位置:
final columnLength = _generateColumns().length; for (int rowIndex = 0; rowIndex < tableRows.length; rowIndex++) { final cellLength = tableRows[rowIndex].cells.length; if (cellLength != columnLength) { debugPrint('异常行位置:第$rowIndex行,单元格数:$cellLength,表头列数:$columnLength'); } }
拿到异常行索引之后,直接对应到列表里的数据项或者占位行逻辑,即可快速定位问题。
内容的提问来源于stack exchange,提问作者Faith.youssef.abk

