API 练习
Flutter 中的复杂 JSON 格式以及如何传递它们?(代码在此)
所有 API(免费且无需身份验证):此处
序列化 简单来说就是将数据(可能是对象中的数据)写成字符串。
反序列化 则相反。由于键总是字符串,值可以是任何类型,为了安全起见,我们将其保留为动态。(Map<String, dynamic>)。
专业提示:使用 compute 方法在后台解析 JSON
为什么?
如果您在较慢的设备上运行 getWhateverApi() 函数,您可能会注意到应用程序在解析和转换 JSON 时会短暂冻结。这就是 **JANK(卡顿)**,您需要摆脱它。
您可以通过将解析和转换移到后台隔离区来消除卡顿,使用 Flutter 提供的 compute() 函数。compute() 函数在后台隔离区运行耗时函数并返回结果。因此,在后台运行 getWhateverApi() 函数。
阅读更多 此处
常见 ${\color{red}错误 :}$
#1. 错误:RangeError (index): 无效值:不在包含范围内 0..5: 6
解决方案:设置 itemCount 属性,这通常发生在 itemCount“未给出”时。
任何 API JSON 数据的常见步骤
#类 在 lib/models/ 中创建一个文件,其中将包含 Model 类、ClassModel.fromJson() 和 ClassModel.toJson() 方法。
-
访问 API 链接。复制数据
-
格式化 JSON 数据:https://jsonformatter.org/
-
创建 DART 对象:https://javiercbk.github.io/json_to_dart/
-
将 DART 对象粘贴到我们之前创建的文件 lib/model/className_model.dart 中。
JSON 格式类型
1. 格式 #1:[ { }, { }, { }…] 或 List [map, map, map…]
#API:https://jsonplaceholder.typicode.com/users
技巧
- 为什么你应该在 buildMethod 之外定义函数并在 initState 中调用它?[ref: 此处]
- Flutter 在每次需要更改视图中的任何内容时都会调用 build() 方法,而且这发生得令人惊讶地频繁。如果 getUserApi() 函数放在 build() 中,它会在每次重建时被反复调用,导致应用程序变慢。
- 将 getUserApi() 的结果存储在状态变量中可确保 Future 只执行一次,然后在后续重建中进行缓存。
#函数:像这样定义函数:(提示:您可以在处理 API 服务的单独文件中定义函数。)
Future<List<UserModel>> getUserApi() async {
List<UserModel> userList = [];
final response =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/users"));
var data = jsonDecode(response.body); //try .toString()
if (response.statusCode == 200) {
for (Map<String, dynamic> item in data) {
// print(item['name']);
userList.add(UserModel.fromJson(item));
}
return userList;
} else {
return userList;
}
}
因为 JSON 的格式是这样的:[ { }, { }, { },…]
- snapshot.data 将包含 List。
- 我们需要提供列表中 Map 的相应索引。
- 要提供索引,我们可以使用什么?……想想!是的,ListView.builder,或者 GridView.builder 或任何可以使用索引遍历的东西(也许是 ${\color{lightgreen}for}$ 循环!)。
- 始终确保提供 itemCount,否则您将遇到上面提到的常见错误 #1。
- 非常 ${\color{red}重要}$: 将 FutureBuilder 类型化为 AsyncSnapshot<List<ClassModel>> snapshot 非常重要。
- 现在您可以使用 snapshot.data![index] 访问数据
- 确保在需要的地方进行空值检查。
#主类
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late Future<List<UserModel>> userList; //late keyword is important
@override
void initState() {
super.initState();
userList = getUserApi(); //calling the function inside initState()
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: getUserApi(),
//Very Imp to typecast snapshot
builder:
(BuildContext context, AsyncSnapshot<List<UserModel>> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
//give itemCount else this error will be thrown : _RangeError (index): Invalid value: Not in inclusive range_
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return Card(
elevation: 2.0,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Name : '),
//use snapshot like this
Text(snapshot.data![index].name.toString()),
],
),
2. 格式 #2:{ [ {}, {}, {}… ] } 或 Map { List [ map, map, map… ] }
#API:https://archive.org/metadata/TheAdventuresOfTomSawyer_201303
示例
{
"files": [
{
"name": "The Adventures of Tom Sawyer.djvu",
"source": "derivative",
"format": "DjVu"
},
{
"name": "The Adventures of Tom Sawyer.gif",
"source": "derivative",
"format": "Animated GIF"
},
{
"name": "TheAdventuresOfTomSawyer_201303_meta.xml",
"source": "original",
"format": "Metadata"
}
]
}
#类:使用相同的程序创建模型类。
#函数
Future<FilesModel> getFilesApi() async {
final response = await http.get(Uri.parse(
"https://archive.org/metadata/TheAdventuresOfTomSawyer_201303"));
var data = jsonDecode(response.body);
if (response.statusCode == 200) {
return FilesModel.fromJson(data);
} else {
return FilesModel.fromJson(data);
}
}
#主类
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
//late keyword is important
late Future<FilesModel> futureFiles;
@override
void initState() {
super.initState();
futureFiles = getFilesApi();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<FilesModel>(
future: futureFiles,
//this time don’t typecast snapshot
builder: (context, snapshot) {
if (snapshot.hasData) {
return SizedBox(
height: 800,
width: double.infinity,
child: GridView.builder(
// shrinkWrap: true,
//give itemCount else this error will be thrown : _RangeError (index): Invalid value: Not in inclusive range_
itemCount: snapshot.data!.filesCount,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2),
//gridPosition is index
itemBuilder: (context, gridPosition) {
return Card(
elevation: 3.0,
child: Column(children: [
Text(
snapshot.data!.files![gridPosition].name!
.toUpperCase(),
style: const TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: Colors.cyan),
),
因为 JSON 的格式是这样的:{ [ { }, { }, { },… ] }
- snapshot.data 将包含 Map。
- 我们不需要在这里提供索引,可以直接使用 [
snapshot.data.](参见上面的代码)。 - 始终确保提供 itemCount,否则您将遇到上面提到的常见错误 #1。
- 非常 ${\color{red}重要}$: 将 FutureBuilder 类型化为 FutureBuilder<ClassModel> 非常重要。
- 现在您可以使用 [
snapshot.data.] 访问数据 - 确保在需要的地方进行空值检查。
结束