
本文档将指导你如何从 PHP API 获取数据,并使用 Flutter 的 `Table` 组件将其动态地展示出来。我们将重点解决 `NoSuchMethodError: The getter 'length' was called on null` 错误,并提供清晰的代码示例和注意事项,确保数据正确加载和显示。
从 PHP API 获取数据
首先,确保你已经创建了一个 Flutter 项目,并且已经添加了 http 依赖。你可以在 pubspec.yaml 文件中添加:
dependencies: http: ^0.13.0 # 请使用最新版本
然后,运行 flutter pub get 来安装依赖。
接下来,创建一个 Model 类来映射 API 返回的数据。根据你提供的 JSON 示例,已经定义了 Model 和 Tender 类。这里再贴一下,方便查阅:
立即学习“PHP免费学习笔记(深入)”;
class Model {
Model({
this.id,
this.goodsRef,
this.loyer,
this.bnCode,
this.loyeeNo,
this.contactName,
this.contactTel,
this.bnDesc,
this.reqStatus,
this.eMail,
this.comments,
this.tender,
this.reqDate,
this.sscOffice,
});
final String id;
final int goodsRef;
final String loyer;
final String bnCode;
final int loyeeNo;
final dynamic contactName;
final dynamic contactTel;
final String bnDesc;
final String reqStatus;
final dynamic eMail;
final String comments;
final List tender;
final DateTime reqDate;
final dynamic sscOffice;
factory Model.fromJson(Map json) => Model(
id: json["\u0024id"] == null ? null : json["\u0024id"],
goodsRef: json["goods_ref"] == null ? null : json["goods_ref"],
loyer: json["loyer"] == null ? null : json["loyer"],
bnCode: json["bn_code"] == null ? null : json["bn_code"],
loyeeNo: json["loyee_no"] == null ? null : json["loyee_no"],
contactName: json["contact_name"],
contactTel: json["contact_tel"],
bnDesc: json["bn_desc"] == null ? null : json["bn_desc"],
reqStatus: json["req_status"] == null ? null : json["req_status"],
eMail: json["e_mail"],
comments: json["comments"] == null ? null : json["comments"],
tender: json["tender"] == null ? null : List.from(json["tender"].map((x) => Tender.fromJson(x))),
reqDate: json["req_date"] == null ? null : DateTime.parse(json["req_date"]),
sscOffice: json["ssc_office"],
);
Map toJson() => {
"\u0024id": id == null ? null : id,
"goods_ref": goodsRef == null ? null : goodsRef,
"loyer": loyer == null ? null : loyer,
"bn_code": bnCode == null ? null : bnCode,
"loyee_no": loyeeNo == null ? null : loyeeNo,
"contact_name": contactName,
"contact_tel": contactTel,
"bn_desc": bnDesc == null ? null : bnDesc,
"req_status": reqStatus == null ? null : reqStatus,
"e_mail": eMail,
"comments": comments == null ? null : comments,
"tender": tender == null ? null : List.from(tender.map((x) => x.toJson())),
"req_date": reqDate == null ? null : reqDate.toIso8601String(),
"ssc_office": sscOffice,
};
}
class Tender {
Tender({
this.id,
this.goodsRef,
this.inNo,
this.tenderNo,
this.closingDate,
});
final String id;
final int goodsRef;
final int inNo;
final String tenderNo;
final String closingDate;
factory Tender.fromJson(Map json) => Tender(
id: json["\u0024id"] == null ? null : json["\u0024id"],
goodsRef: json["goods_ref"] == null ? null : json["goods_ref"],
inNo: json["in_no"] == null ? null : json["in_no"],
tenderNo: json["tender_no"] == null ? null : json["tender_no"],
closingDate: json["closing_date"] == null ? null : json["closing_date"],
);
Map toJson() => {
"\u0024id": id == null ? null : id,
"goods_ref": goodsRef == null ? null : goodsRef,
"in_no": inNo == null ? null : inNo,
"tender_no": tenderNo == null ? null : tenderNo,
"closing_date": closingDate == null ? null : closingDate,
};
} 接下来,创建一个函数来从 API 获取数据:
import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; Future> fetchItems(String email) async { String apiurl = "YOUR_API_URL"; // 替换为你的 API URL var response = await http.post(Uri.parse(apiurl), body: { 'username': email // 获取用户名 }); if (response.statusCode == 200) { // 使用 utf8.decode 处理中文乱码问题 final decodedBody = utf8.decode(response.bodyBytes); List
jsonResponse = jsonDecode(decodedBody); List model = jsonResponse.map((item) => Model.fromJson(item)).toList(); return model; } else { throw Exception('Failed to load data from API'); } }
注意:
- 将 YOUR_API_URL 替换为你的 PHP API 的实际 URL。
- 使用了 utf8.decode(response.bodyBytes) 来处理中文乱码问题,确保API返回的编码是UTF-8。
在 Flutter Table 中展示数据
现在,我们可以使用 FutureBuilder 来异步获取数据,并在 Table 组件中展示数据。
婚纱影楼小程序提供了一个连接用户与影楼的平台,相当于影楼在微信的官网。它能帮助影楼展示拍摄实力,记录访客数据,宣传优惠活动。使用频率高,方便传播,是影楼在微信端宣传营销的得力助手。功能特点:样片页是影楼展示优秀摄影样片提供给用户欣赏并且吸引客户的。套系页是影楼根据市场需求推出的不同套餐,用户可以按照自己的喜好预定套系。个人中心可以查看用户预约的拍摄计划,也可以获取到影楼的联系方式。
class MyTable extends StatefulWidget {
@override
_MyTableState createState() => _MyTableState();
}
class _MyTableState extends State {
String email = "test@example.com"; // 替换为你的邮箱
Future> _dataFuture;
@override
void initState() {
super.initState();
_dataFuture = fetchItems(email);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Data Table from API')),
body: FutureBuilder>(
future: _dataFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (snapshot.hasData) {
return buildTable(snapshot.data);
} else {
return Center(child: Text('No data available'));
}
},
),
);
}
Widget buildTable(List data) {
return SingleChildScrollView( // 确保表格在小屏幕上可以滚动
scrollDirection: Axis.horizontal,
child: Table(
border: TableBorder.all(width: 1, color: Colors.black45),
columnWidths: {
0: FixedColumnWidth(100.0), // 可以自定义列宽
1: FixedColumnWidth(150.0),
2: FixedColumnWidth(200.0),
3: FixedColumnWidth(100.0),
},
children: [
TableRow( // 表头
children: [
TableCell(child: Center(child: Padding(padding: EdgeInsets.all(5), child: Text('Goods Ref')))),
TableCell(child: Center(child: Padding(padding: EdgeInsets.all(5), child: Text('BN Code')))),
TableCell(child: Center(child: Padding(padding: EdgeInsets.all(5), child: Text('BN Desc')))),
TableCell(child: Center(child: Padding(padding: EdgeInsets.all(5), child: Text('Req Status')))),
],
),
...data.map((item) {
return TableRow(
children: [
TableCell(child: Center(child: Padding(padding: EdgeInsets.all(5), child: Text(item.goodsRef?.toString() ?? '')))),
TableCell(child: Center(child: Padding(padding: EdgeInsets.all(5), child: Text(item.bnCode ?? '')))),
TableCell(child: Center(child: Padding(padding: EdgeInsets.all(5), child: Text(item.bnDesc ?? '')))),
TableCell(child: Center(child: Padding(padding: EdgeInsets.all(5), child: Text(item.reqStatus ?? '')))),
],
);
}).toList(),
],
),
);
}
}
关键点:
- 使用 FutureBuilder 来处理异步数据加载。
- 在 buildTable 方法中,使用 data.map 来迭代数据并创建 TableRow。
- 使用 item.propertyName ?? '' 来处理可能为 null 的值,避免 NoSuchMethodError 错误。 ?? 是 Dart 的 null-aware 运算符,如果 item.propertyName 为 null,则使用空字符串 '' 作为默认值。
- SingleChildScrollView 确保表格在小屏幕上可以滚动。
- 添加了表头,使表格更易于理解。
- 使用 columnWidths 来自定义列宽。
解决 NoSuchMethodError 错误
NoSuchMethodError: The getter 'length' was called on null 错误通常发生在尝试访问 null 值的属性时。在你的例子中,这很可能是因为 API 返回的数据中某些字段是 null,而你没有正确处理。
解决方法:
使用 null-aware 运算符 (??): 在访问可能为 null 的属性时,使用 ?? 运算符提供一个默认值。例如:Text(item.name ?? '')。
检查 null 值: 在访问属性之前,可以使用 if (item.property != null) 来检查值是否为 null。
-
在 Model 类中处理 null 值: 在 Model 类的 fromJson 方法中,可以为可能为 null 的字段提供默认值。例如:
factory Model.fromJson(Map
json) => Model( // ... bnDesc: json["bn_desc"] == null ? "" : json["bn_desc"], // ... );
总结
本文档介绍了如何从 PHP API 获取数据,并在 Flutter 的 Table 组件中展示数据。通过使用 FutureBuilder、null-aware 运算符和适当的错误处理,你可以创建一个动态的、数据驱动的表格。记住,处理 API 返回的 null 值是避免 NoSuchMethodError 错误的关键。希望本教程能够帮助你成功地构建你的 Flutter 应用!










