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" } ] }
问题原因分析:
- FutureBuilder数据赋值错误:
fetchApplicants返回的是整个JSON对象(Map类型),但在builder里直接把snapshot.data赋值给applicants,导致applicants变成Map而非数组,后续通过applicants[index]访问会出错。 - 函数返回类型不匹配:
fetchApplicants声明返回Future<List?>,但实际返回Map类型的JSON对象,类型不匹配。 - 数字转字符串缺失:
data['id']是整数类型,直接传给Text组件会报错。 - 错误处理缺失:请求失败时(如状态码≠200),
fetchApplicants无返回值,也未处理snapshot.hasError的情况。 - 冗余的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
相关产品推荐
相关产品推荐

