Flutter如何将API响应值渲染到静态列垂直DataTable的行区域
问题根因
代码运行不符合预期核心是3个逻辑错误:
getRows方法完全没处理Map的Key,仅把所有Value塞入单元格,没有按「Key放TITLE列、Value放VALUE列」的规则生成每行2个单元格的结构,若返回Map字段数不等于2,生成的DataRow单元格数和预设的2个DataColumn数量不匹配,会直接报错无法渲染。- API返回值解析逻辑和实际接口结构不匹配:目标mock接口返回的是单个Map对象而非
List<Map>,强转List会触发类型错误。 - 主页面初始化变量时未做类型适配,首次build传入组件的参数类型和组件接收类型不匹配,导致加载状态卡住。
修正步骤
1. 修正API服务解析逻辑
删除源码中类名旁的乱码字符,调整返回类型适配单对象结构,去掉无效强转:
import 'dart:developer'; import 'dart:io'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'app_exceptions.dart'; class ApiServiceHandler { // 适配接口单个Map返回结构 Future<Map<String, dynamic>> getTableVerticalPerson( String urlEndPoint) async { log('Api Get url, $urlEndPoint'); dynamic responseJson; try { final response = await http.get(Uri.parse(urlEndPoint)); if (response.statusCode >= 200 && response.statusCode < 300) { print("Response Body: ${response.body}"); responseJson = _returnResponse(response); print("Response JSON: ${responseJson.toString()}"); } else { log('UnSuccess'); log("The error message is: ${response.body}"); } } on SocketException { log('No Internet'); throw FetchDataException('No Internet connection'); } log('api get received !'); return responseJson; } } dynamic _returnResponse(http.Response response) { switch (response.statusCode) { case 200: var responseJson = json.decode(response.body.toString()); log("VERTICAL DATA RESPONSE $responseJson"); return responseJson; case 400: throw BadRequestException(response.body.toString()); case 401: case 403: throw UnauthorisedException(response.body.toString()); case 500: default: throw FetchDataException( 'ERROR OCCURRED WHILE COMMUNICATION WITH SERVER WITH STATUS CODE : ${response.statusCode}'); } }
2. 重写表格组件行生成逻辑
调整组件入参类型为单个Map结构,重写getRows方法,严格按每行2个单元格(第一格存Key、第二格存对应Value)生成DataRow:
import 'package:adaptive_scrollbar/adaptive_scrollbar.dart'; import 'package:flutter/material.dart'; class VerticalTableWidgetCommon extends StatelessWidget { final Map<String, dynamic>? verticalData; const VerticalTableWidgetCommon({this.verticalData, Key? key}) : super(key: key); @override Widget build(BuildContext context) { final verticalScrollController = ScrollController(); final horizontalScrollController = ScrollController(); return Scaffold( body: SafeArea( child: AdaptiveScrollbar( underColor: Colors.blueGrey.withOpacity(0.3), sliderDefaultColor: Colors.grey.withOpacity(0.7), sliderActiveColor: Colors.grey, width: 17, controller: verticalScrollController, child: AdaptiveScrollbar( controller: horizontalScrollController, position: ScrollbarPosition.bottom, underColor: Colors.grey.withOpacity(0.3), sliderDefaultColor: Colors.grey.withOpacity(0.7), sliderActiveColor: Colors.grey, width: 17, child: verticalData != null ? SingleChildScrollView( controller: verticalScrollController, scrollDirection: Axis.vertical, child: SingleChildScrollView( controller: horizontalScrollController, scrollDirection: Axis.horizontal, child: DataTable( border: TableBorder( verticalInside: BorderSide( width: 0.4, color: Colors.grey.shade600), horizontalInside: BorderSide( width: 0.4, color: Colors.grey.shade600), left: BorderSide( width: 0.0, color: Colors.grey.shade100), right: BorderSide( width: 0.0, color: Colors.grey.shade100), top: BorderSide( width: 0.1, color: Colors.grey.shade100), bottom: BorderSide( width: 0.1, color: Colors.grey.shade600)), headingTextStyle: TextStyle( fontWeight: FontWeight.normal, fontSize: 12, color: Colors.grey.shade600), dataTextStyle: TextStyle( fontWeight: FontWeight.normal, fontSize: 12, color: Colors.grey.shade600), headingRowHeight: 25, dataRowHeight: 25, headingRowColor: MaterialStateColor.resolveWith( (states) => Colors.grey.shade100, ), dividerThickness: 1.0, columnSpacing: 20.0, horizontalMargin: 5.0, showBottomBorder: false, decoration: BoxDecoration( border: Border.all( width: 0.5, color: Colors.grey.shade600, )), columns: [ DataColumn( label: SizedBox( width: MediaQuery.of(context).size.width * 0.20, child: const Center(child: Text('TITLE')), )), DataColumn( label: SizedBox( width: MediaQuery.of(context).size.width * 0.80, child: const Center(child: Text('VALUE')))), ], rows: getRows(verticalData!)), ), ) : const Center( child: CircularProgressIndicator( color: Colors.blue, )), ), ), ), ); } List<DataRow> getRows(Map<String, dynamic> data) { List<DataRow> rowList = []; int index = 0; // 遍历每个键值对,单个键值对生成一行 data.forEach((key, value) { rowList.add( DataRow( cells: [ // 第一列放Key DataCell( Text(key, style: const TextStyle( fontWeight: FontWeight.normal, fontSize: 12, color: Colors.black87)), ), // 第二列放对应Value DataCell( Text(value.toString(), style: const TextStyle( fontWeight: FontWeight.normal, fontSize: 12, color: Colors.black87)), ) ], color: index.isEven ? MaterialStateColor.resolveWith((states) => Colors.white) : MaterialStateColor.resolveWith( (states) => Colors.grey.shade100), ), ); index++; }); return rowList; } }
3. 修正主页面调用逻辑
调整变量类型匹配修改后的API返回结构,正确触发setState更新:
import 'package:flutter/material.dart'; import 'package:api_calling/api_service_handler.dart'; import 'package:commons/vertical_table_widget_common.dart'; class VerticalApiCallingDemo extends StatefulWidget { const VerticalApiCallingDemo({Key? key}) : super(key: key); @override State<VerticalApiCallingDemo> createState() => _VerticalApiCallingDemoState(); } class _VerticalApiCallingDemoState extends State<VerticalApiCallingDemo> { Map<String, dynamic>? verticalTableData; @override void initState() { super.initState(); ApiServiceHandler() .getTableVerticalPerson( "https://run.mocky.io/v3/a0e60aed-b553-4031-915e-ab90d9d6152c") .then((value) { setState(() { verticalTableData = value; }); }); } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Center( child: VerticalTableWidgetCommon(verticalData: verticalTableData), )), ); } }
额外注意
DataTable要求每一行的DataCell数量必须和DataColumn数量完全一致,禁止生成数量不匹配的单元格,否则会直接抛出布局异常。
内容的提问来源于stack exchange,提问作者nitish chauhan
相关产品推荐
相关产品推荐

