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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 22:06:23