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() 方法。

  1. 访问 API 链接。复制数据

  2. 格式化 JSON 数据:https://jsonformatter.org/

  3. 创建 DART 对象:https://javiercbk.github.io/json_to_dart/

  4. 将 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.] 访问数据
  • 确保在需要的地方进行空值检查。

结束

GitHub

查看 Github