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

Flutter中API数据无法填充DataTable问题求助

问题:Flutter中DataTable无法通过JSON API填充数据,始终显示"No data found..."

参考Stack Overflow相关回答修改代码后,DataTable始终无法加载API返回的数据,持续显示"No data found..."。API返回数据格式正常,但代码运行后无数据展示。

原代码:

List applicants = [];
String applicantsByDate = "http://localhost:3000/applicants/getApplicantsByDate";

DataRow _resultsAPI(index, data) {
  return DataRow(
    cells: <DataCell>[
      DataCell(
        Text(
          data['id'],
        ),
      ), //add name of your columns here
      DataCell(
        Text(
          data['firstName'],
        ),
      ),
      DataCell(
        Text(
          data['lastName'],
        ),
      ),
      DataCell(
        Text(
          data['createdAt'],
        ),
      )
    ],
  );
}

Future<List?> fetchApplicants() async {
    http.Response response = await http.get(Uri.parse(applicantsByDate));
    if (response.statusCode == 200) {
      var data = jsonDecode(response.body);
      setState(() {
        applicants = data['applicantsByDate'];
      });
      return jsonDecode(response.body);
    }
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    resizeToAvoidBottomInset: false,
    appBar: AppBar(
      leading: Padding(
        padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
        child: Image.asset(
            "images/Logo.png",
            fit: BoxFit.cover,
            height: 20.00,
            width: 20.00),
      ),
      leadingWidth: 130,
      title: const Text('Applicants'),
    ),
    body: SingleChildScrollView(
      child: SafeArea(
        child: Container(
          padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
          child: FutureBuilder(
            future: fetchApplicants(),
            builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
              if (snapshot.hasData) {
                applicants = snapshot.data;
                if (applicants.length != 0) {
                  return Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      const SizedBox(
                        height: 30.0,
                      ),
                      Row(
                        mainAxisAlignment: MainAxisAlignment.start,
                        children: <Widget>[
                          Expanded(
                            child: Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              crossAxisAlignment: CrossAxisAlignment.stretch,
                              children: [
                                DataTable(
                                  columns: const [
                                    DataColumn(
                                      label: Text(
                                        'ID',
                                        style: TextStyle(
                                            color: Palette.coloresCRC,
                                            fontWeight: FontWeight.w600,
                                            fontSize: 18),
                                      ),
                                    ),
                                    DataColumn(
                                      label: Text(
                                        'First Name',
                                        style: TextStyle(
                                            color: Palette.coloresCRC,
                                            fontWeight: FontWeight.w600,
                                            fontSize: 18),
                                      ),
                                    ),
                                    DataColumn(
                                      label: Text(
                                        'Last Name',
                                        style: TextStyle(
                                            color: Palette.coloresCRC,
                                            fontWeight: FontWeight.w600,
                                            fontSize: 18),
                                      ),
                                    ),
                                    DataColumn(
                                      label: Text(
                                        'Date Applied',
                                        style: TextStyle(
                                            color: Palette.coloresCRC,
                                            fontWeight: FontWeight.w600,
                                            fontSize: 18),
                                      ),
                                    ),
                                  ],
                                  rows: List.generate(
                                    applicants.length,
                                    (index) => _resultsAPI(
                                      index,
                                      applicants[index],
                                    ),
                                  ),
                                ),
                              ],
                            ),
                          ),
                        ],
                      ),
                    ],
                  );
                } else {
                  return Row(
                    children: const <Widget>[
                      SizedBox(
                        // ignore: sort_child_properties_last
                        child: CircularProgressIndicator(),
                        width: 30,
                        height: 30,
                      ),
                      Padding(
                        padding: EdgeInsets.all(40),
                        child: Text('No Data Found...'),
                      ),
                    ],
                  );
                }
              } else {
                return Row(
                  children: const <Widget>[
                    SizedBox(
                      // ignore: sort_child_properties_last
                      child: CircularProgressIndicator(),
                      width: 30,
                      height: 30,
                    ),
                    Padding(
                      padding: EdgeInsets.all(40),
                      child: Text('No Data Found...'),
                    ),
                  ],
                );
              }
            },
          ),
        ),
      ),
    ),
  );
}

API返回数据格式:

{
    "applicantsByDate": [
        {
            "id": 1007,
            "firstName": "Andrea",
            "lastName": "Savillon",
            "createdAt": "2022-12-14T19:58:57.431Z"
        },
        {
            "id": 1006,
            "firstName": "Emily",
            "lastName": "Savillon",
            "createdAt": "2022-12-14T19:56:42.795Z"
        },
        {
            "id": 1005,
            "firstName": "Andres",
            "lastName": "Abadie",
            "createdAt": "2022-12-14T19:54:03.424Z"
        },
        {
            "id": 1004,
            "firstName": "Carlos",
            "lastName": "Marroquin",
            "createdAt": "2022-12-14T18:05:42.157Z"
        },
        {
            "id": 1003,
            "firstName": "Sara",
            "lastName": "Savillon",
            "createdAt": "2022-12-14T18:03:41.343Z"
        },
        {
            "id": 1002,
            "firstName": "Alexander",
            "lastName": "Savillon",
            "createdAt": "2022-12-14T17:28:38.909Z"
        },
        {
            "id": 2,
            "firstName": "Miguel",
            "lastName": "Madrid",
            "createdAt": "2022-12-13T21:59:48.593Z"
        },
        {
            "id": 1,
            "firstName": "Eder",
            "lastName": "Savillon",
            "createdAt": "2022-12-13T14:54:42.118Z"
        }
    ]
}

问题原因分析:

  1. FutureBuilder数据赋值错误:fetchApplicants返回的是整个JSON对象(Map类型),但在builder里直接把snapshot.data赋值给applicants,导致applicants变成Map而非数组,后续通过applicants[index]访问会出错。
  2. 函数返回类型不匹配:fetchApplicants声明返回Future<List?>,但实际返回Map类型的JSON对象,类型不匹配。
  3. 数字转字符串缺失:data['id']是整数类型,直接传给Text组件会报错。
  4. 错误处理缺失:请求失败时(如状态码≠200),fetchApplicants无返回值,也未处理snapshot.hasError的情况。
  5. 冗余的setState:FutureBuilder会自动监听Future状态变化,无需在fetchApplicants里调用setState。

修正后的代码:

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

// 假设Palette已定义
class Palette {
  static const coloresCRC = Colors.blue;
}

class ApplicantsScreen extends StatefulWidget {
  const ApplicantsScreen({super.key});

  @override
  State<ApplicantsScreen> createState() => _ApplicantsScreenState();
}

class _ApplicantsScreenState extends State<ApplicantsScreen> {
  final String applicantsByDate = "http://localhost:3000/applicants/getApplicantsByDate";

  DataRow _resultsAPI(index, data) {
    return DataRow(
      cells: <DataCell>[
        DataCell(Text(data['id'].toString())), // 数字转字符串
        DataCell(Text(data['firstName'])),
        DataCell(Text(data['lastName'])),
        DataCell(Text(data['createdAt'])),
      ],
    );
  }

  // 修改返回类型为Future<Map<String, dynamic>>,匹配API返回格式
  Future<Map<String, dynamic>> fetchApplicants() async {
    try {
      final response = await http.get(Uri.parse(applicantsByDate));
      if (response.statusCode == 200) {
        return jsonDecode(response.body);
      } else {
        throw Exception('请求失败,状态码:${response.statusCode}');
      }
    } catch (e) {
      throw Exception('请求出错:$e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        leading: Padding(
          padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
          child: Image.asset(
            "images/Logo.png",
            fit: BoxFit.cover,
            height: 20.00,
            width: 20.00,
          ),
        ),
        leadingWidth: 130,
        title: const Text('Applicants'),
      ),
      body: SingleChildScrollView(
        child: SafeArea(
          child: Container(
            padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
            child: FutureBuilder<Map<String, dynamic>>(
              future: fetchApplicants(),
              builder: (context, snapshot) {
                // 加载中状态
                if (snapshot.connectionState == ConnectionState.waiting) {
                  return const Row(
                    children: [
                      SizedBox(
                        child: CircularProgressIndicator(),
                        width: 30,
                        height: 30,
                      ),
                      Padding(
                        padding: EdgeInsets.all(40),
                        child: Text('加载中...'),
                      ),
                    ],
                  );
                }
                // 请求出错状态
                else if (snapshot.hasError) {
                  return Row(
                    children: [
                      const Icon(Icons.error, color: Colors.red),
                      Padding(
                        padding: const EdgeInsets.all(40),
                        child: Text('请求出错:${snapshot.error}'),
                      ),
                    ],
                  );
                }
                // 数据加载完成
                else if (snapshot.hasData) {
                  final applicants = snapshot.data!['applicantsByDate'] as List;
                  if (applicants.isNotEmpty) {
                    return Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        const SizedBox(height: 30.0),
                        Row(
                          mainAxisAlignment: MainAxisAlignment.start,
                          children: <Widget>[
                            Expanded(
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                crossAxisAlignment: CrossAxisAlignment.stretch,
                                children: [
                                  DataTable(
                                    columns: const [
                                      DataColumn(
                                        label: Text(
                                          'ID',
                                          style: TextStyle(
                                              color: Palette.coloresCRC,
                                              fontWeight: FontWeight.w600,
                                              fontSize: 18),
                                        ),
                                      ),
                                      DataColumn(
                                        label: Text(
                                          'First Name',
                                          style: TextStyle(
                                              color: Palette.coloresCRC,
                                              fontWeight: FontWeight.w600,
                                              fontSize: 18),
                                        ),
                                      ),
                                      DataColumn(
                                        label: Text(
                                          'Last Name',
                                          style: TextStyle(
                                              color: Palette.coloresCRC,
                                              fontWeight: FontWeight.w600,
                                              fontSize: 18),
                                        ),
                                      ),
                                      DataColumn(
                                        label: Text(
                                          'Date Applied',
                                          style: TextStyle(
                                              color: Palette.coloresCRC,
                                              fontWeight: FontWeight.w600,
                                              fontSize: 18),
                                        ),
                                      ),
                                    ],
                                    rows: List.generate(
                                      applicants.length,
                                      (index) => _resultsAPI(index, applicants[index]),
                                    ),
                                  ),
                                ],
                              ),
                            ),
                          ],
                        ),
                      ],
                    );
                  } else {
                    return const Row(
                      children: [
                        Padding(
                          padding: EdgeInsets.all(40),
                          child: Text('No Data Found...'),
                        ),
                      ],
                    );
                  }
                }
                // 默认状态
                else {
                  return const Text('无数据');
                }
              },
            ),
          ),
        ),
      ),
    );
  }
}

关键修改点:

  • 调整fetchApplicants返回类型为Future<Map<String, dynamic>>,添加错误捕获和处理逻辑。
  • 在FutureBuilder中正确提取snapshot.data['applicantsByDate']作为数组使用。
  • 将data['id']转为字符串,避免Text组件类型错误。
  • 拆分状态判断:加载中、请求出错、数据非空、数据为空,逻辑更清晰。
  • 移除fetchApplicants中冗余的setState调用。

内容的提问来源于stack exchange,提问作者Eder Savillon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 06:25:06