Flutter:解析JSON

小红星闪啊闪

看到有同学想要代码,这个在原文中都有。我也正在开发一个app,代码比原文里的还要接近实际使用。

准备

如果可以的话还是请看原文。我这都按照我的理解翻译的,仅供参考。

如果一个App界面上什么都没有的话,那么绝对够无聊的。但是你的app从哪里可以获得有趣的内容呢?必须是网络了。你的,你公司的后端或者是网络上的公开API!

很多的网站提供了REST API,一般只要注册就可以使用他们的API。在本文中会用到一个瞄星人的站点,你会在那里注册。并把数据展示在一个Flutter app里。在这个站点的API里你会获得一个喵星人的列表,每个item里面还有一些数据以及喵星人的图片。

JSON:是JavaScript Object Notation的缩写,基本上所有的API都在用这个数据格式。在本文中你会学到如何把JSON串解析成一个model类的对象,如何把它显示在屏幕上。详细内容包括:

  • 调用网络API
  • 解析JSON数据
  • 把数据显示在一个ListView里。
  • 显示网络图片

开始

本教程的代码在这里,点击下载材料获取。

本文会使用Android Studio和Flutter插件来开发。你也可以使用Visual Studio Code,IntelliJ IDEA来开发。要给Android Studio装Flutter插件,找到Plugins

点击“市场”,找到Flutter,之后点击安装按钮。安装了这个plugin之后也就安装了dart plugin了。如果没有自动安装的话,手动安装一下。

这些plugin都装好之后,重启android studio。在开始界面上选择打开一个已经存在的Android Studio项目然后找到下载好的代码的根目录:


Android Studio会弹出一个框获取项目路里用到的包。继续,之后你会看到:

所有的准备工作完成之后,在设备下拉表里选择一个iOS模拟器或者android模拟器,当然如果你用的是mac,而且Xcode也安装了的话。点击运行按钮。

开始项目就会运行起来,在iOS模拟器是这样的:

Android模拟器是这样的:

理解UI

现在屏幕上还没有任何的数据。接下来就要把数据加上去。

breed, 这里是指猫的品种。

打开lib/screens/cat_breeds.dart文件你会看到如下的代码:

import 'package:flutter/material.dart';
import 'cat_info.dart';

class CatBreedsPage extends StatefulWidget {
  // 1
  CatBreedsPage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _CatBreedsPageState createState() => _CatBreedsPageState();
}
class _CatBreedsPageState extends State<CatBreedsPage> {
  @override
  void initState() {
    super.initState();
  }
  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        // 2
        title: Text(widget.title),
      ),
      // 3
      body: ListView.builder(
          // 4
          itemCount: 0,
          itemBuilder: (context, index) {
            // 5
            return GestureDetector(
              onTap: () {
                Navigator.push(context, MaterialPageRoute(builder: (context) {
                  return CatInfo(catId: 'id', catBreed: 'Name');
                }));
              },
              // 6
              child: Card(
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  // 7
                  child: ListTile(
                    title: Text('Breed Name'),
                    subtitle: Text('Breed Description'),
                  ),
                ),
              ),
            );
          }),
    );
  }
}

解释:

  1. 构建一个CatBreedsPage
  2. AppBar里设置title字段的值
  3. ListView.builder作为字段的方法
  4. count设置为0,毕竟现在还没有任何的数据
  5. 使用Navigator类跳转到CatInfo详情页
  6. 创建一个Card,里面放一个Padding
  7. 添加一个包含title和descriptin的ListTile

使用REST API

请求REST API的时候有不同的method,一般是GET:用来获取数据,也有POST来保存数据,PATCHPUT用来更新数据。另外还有个DELETE method,用来删除数据。

如果你看过喵星人API,你就会你可以使用的请求method。如果你点开Search by Breed连接,你会发现需要一个API key才能用这些API。

注册Cats API

跳转到这里注册一个账号。这一步是必须的,否则的话那些API都没法调用。

调用网络API

请求API在dart来说是一件很轻松的事。你只需要使用开始项目里的HTTP库。打开lib/api/network.dart文件,代码是这样的:

// 1
import 'package:http/http.dart';

class Network {
  final String url;
  //2
  Network(this.url);

  // 3
  Future getData() async {
    print('Calling uri: $url');
    // 4
    Response response = await get(url);
    // 5
    if (response.statusCode == 200) {
      // 6
      return response.body;
    } else {
      print(response.statusCode);
    }
  }
}

解释如下:

  1. 引入HTTP库
  2. Network类有一个接受一个字符串为参数的构造函数
  3. 包含了一个异步方法getData()
  4. 使用HTTP GET method请求url,并等待返回
  5. 检查状态码,如果是200那么返回是OK的,否则就是一个Error
  6. 返回结果

理解JSON

JSON大多数REST API返回的数据的格式。另外一个通用格式是XML。

JSON实例:

{
  "user": {
      "name": "Kevin Moore",
      "occupation": "Programmer"
   }
}

上面的例子是从一个{大括号开始,说明是一个对象数据。JSON也可以是一个数组,这时候一般是[开头。JSON格式不能出错,也就是又开始就需要有结束,当它是一个对象的时候就需要有结束的大括号}。

在服务端返回了一个JSON串之后,你可以:

  1. 从字符串里获得key/value对
  2. 把字符串转化为一个dart的Map对象,再从里面拿到key/value对。
  3. 把字符串转化为一个model类的对象,然后从这个对象的属性里获得数据

以上方法都可以获取到数据。但是最好的方法还是把JSON串转化为model类的对象。

解析JSON

我们来看看有哪些方法可以用来解析JSON。

手动解析

你可以使用dart:convert库来解析:

import 'dart:convert';

Map<String, dynamic> user = jsonDecode(jsonString);
var name = user['user]['name'];

这看起来没什么难的。但是如果处理复杂的JSON,代码就会变的冗长繁复。

使用库

pub.dev,可以找到处理JSON的Flutter库:

  • HTTP用来处理网络请求。
  • json_annotation用来给你的model类添加注解

你会发现两个工具库可以创建把json串转化为model对象的工具方法:

  • build_runner,运行json_serializable库
  • json_serializable,创建额外的把json串转化为model对象的工具代码

这俩个库要放在pubspec.yaml文件的dev_dependencies后面。

最后,在pubspec.yaml文件的dependencies里:

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2
  http: ^0.12.0+2
  json_annotation: ^2.0.0 

dev_dependencies里:

dev_dependencies:
  flutter_test:
    sdk: flutter    
  build_runner: ^1.0.0
  json_serializable: ^2.0.0

接下来点击在android studio的右上角出现的Packages get。我们需要的包就回下载下来了。

Cat API

打开lib/api/cats_api.dart文件。第一行就是叫做apiKey的字符串常量。用你自己的key放进去。

const String apiKey = 'Your Key';
//1
const String catAPIURL = 'https://api.thecatapi.com/v1/breeds?';
// 2
const String catImageAPIURL = 'https://api.thecatapi.com/v1/images/search?';
// 3
const String breedString = 'breed_id=';
// 4
const String apiKeyString = 'x-api-key=$apiKey';

class CatAPI {
  // 5
  Future<dynamic> getCatBreeds() async {
     // 6
    Network network = Network('$catAPIURL$apiKeyString');
    // 7
    var catData = await network.getData();
    return catData;
  }
  // 8
  Future<dynamic> getCatBreed(String breedName) async {
    Network network =
        Network('$catImageAPIURL$breedString$breedName&$apiKeyString');
    var catData = await network.getData();
    return catData;
  }
}

解释如下:

  1. API的url字符串,获取喵星人列表
  2. 同样是url串,查找喵星人的图
  3. 获取一个breed ID
  4. 存放你的key的字符串
  5. 获取列表的方法getCatBreeds()
  6. Network类,需要传入API的url和你的key组成的字符串
  7. 获取某个breed的喵的图片的方法getCatBreed(String breedName)

使用Cat API

在文件lib/screens/cat_breeds.dart文件中的_CatBreedsPageState,里添加如下代码:

void getCatData() async {
  var result = await CatAPI().getCatBreeds();
  print(result);
}

这个方法会获得喵的breeds。

这里需要从cat_info.dart文件引入CatAPI类。你可以手动实现,也可以把光标移到CatAPI上,然后使用快捷键Option+Enter,选择Import

接下来在initState()方法下面添加这个方法:

getCatData();

现在可以运行代码了,你应该可以发现请求API得到的JSON串了:

创建Model类

在Models目录下打开cats.dart文件。你会发现注释掉的JSON串。

添加一个描述喵种的类

class Breed {
  String id;
  String name;
  String description;
  String temperament;

  Breed({this.id, this.name, this.description, this.temperament});
}

这个类的字段基本上和从API里获得的数据的字段一致。id用来获取喵种的图片,namedescription用来在列表的CardView中显示。

喵种的列表数据就是从[开始,到]结束的一个JSON数组。

class BreedList {
  List<Breed> breeds;

  BreedList({this.breeds});
}

这个类里面包含了breeds列表。

要查找喵种的图片,还需要cat的model类,cat breed的model类以及cat breed列表的model类。添加下面的代码到cats.dart文件里:

class Cat {
  String name;
  String description;
  String life_span;

  Cat({this.name, this.description, this.life_span});
}

class CatBreed {
  String id;
  String url;
  int width;
  int height;
  List<Cat> breeds;

  CatBreed({this.id, this.url, this.width, this.height, this.breeds});
}

class CatList {
  List<CatBreed> breeds;

  CatList({this.breeds});
}

在教程的app里是不需要用到temperamentlife_span字段的,当然你可以用这两个字段丰富app的功能。

使用JSON注解

现在可以使用json_annotation库来把数据解析到model类的对象里。

cats.dart文件里添加如下代码:

import 'package:json_annotation/json_annotation.dart';
part 'cats.g.dart';

part语句允许你引入一个文件,并使用里面的私有变量。现在会显示一个错误。但是用build_runner文件cats.g.dart之后就不会了。

现在给每个model类添加注解:@JsonSerializable()。如:

@JsonSerializable()
class Breed {
  String id;
  String name;
  String description;
  String temperament;

  Breed({this.id, this.name, this.description, this.temperament});
}

JSON转换方法

在这一节,我们会给每个类都加上一个工厂方法。build runner会根据这些方法生成专门处理数据转化的代码。

Breed类的构造函数后面添加如下代码:

factory Breed.fromJson(Map<String, dynamic> json) => _$BreedFromJson(json);

Map<String, dynamic> toJson() => _$BreedToJson(this);

每个类都会包含一个fromJson和一个toJson()方法,这两个方法会调用生成的数据转换方法。现在Android Stuido里会显示一些错误,暂时忽略。

BreedList类的构造函数后面添加如下代码:

factory BreedList.fromJson(List<dynamic> json) {
  return BreedList(
      breeds: json
          .map((e) => Breed.fromJson(e as Map<String, dynamic>))
          .toList());
}

Cat类里添加fromJsontoJson两个方法:

factory Cat.fromJson(Map<String, dynamic> json) => _$CatFromJson(json);

Map<String, dynamic> toJson() => _$CatToJson(this);

最后在CatList类的构造函数后面添加如下代码:

factory CatList.fromJson(List<dynamic> json) {
  return CatList(
      breeds: json
          .map((e) => CatBreed.fromJson(e as Map<String, dynamic>))
          .toList());
}

使用Build Runner

在项目的终端里运行如下的命令:

flutter packages pub run build_runner build

如果没什么问题就会生出出来我们前文反复提到的cats.g.dart文件。

使用Model类

现在Nodel类都已经完备了,可以用起来了。

开始前,现在_CatBreedsPageState里添加一个属性:

class _CatBreedsPageState extends State<CatBreedsPage> {  
  BreedList breedList = BreedList(); //<-添加这个属性
  ...

引入
引入cats.dart文件。添加dart:convert
getCatData()方法里,在print语句后面添加如下的代码:

// 1
var catMap = json.decode(result);
// 2 
setState(() {
  // 3
  breedList = BreedList.fromJson(catMap);
});

解释如下:

  1. 使用json.decode(result)从JSON串得到一个map
  2. 调用setState来重绘Widget,因为数据已经发生了变化
  3. 使用fromJson(catMap)把得到的map转化为一个喵种的列表

接下来就该把数据显示在界面里了。

跳转到body: ListView.builder语句,把itemCount:0替换为:

itemCount: (breedList == null || breedList.breeds == null || breedList.breeds.length == 0) ? 0 : breedList.breeds.length,

这样就会把itemCount设置为实际数量的item值。

ListTile里的titlesubTitle替换为如下的代码:

title: Text(breedList.breeds[index].name),
subtitle: Text(breedList.breeds[index].description),

现在运行代码,会出现如下的界面了:

祝贺你!!!

创建详情页

下一步添加onTap,在用户点击了一行的时候可以跳转到详情页,并且显示出这个喵种的图片。使用下面的代码替换onTap里的代码:

return CatInfo(catId: breedList.breeds[index].id, catBreed: breedList.breeds[index].name);

这样就把点击那行的idname都传到了CatInfo的构造函数里。

现在打开lib/screens/cat_info.dart文件在_CatInfoState类的initState上面添加下面的代码:

CatList catList = CatList();

void getCatData() async {
  var catJson = await CatAPI().getCatBreed(widget.catId);
  print(catJson);

  var catMap = json.decode(catJson);
  print(catMap);
  setState(() {
    catList = CatList.fromJson(catMap);
  });
}

initState方法里添加对getCatData()的调用。

@override
void initState() {
  super.initState();
  getCatData();
}

确保import了所有需要的文件。

getCat()方法里, 在mediaSize属性声明的后面添加如下代码:

  if (catList == null ||
      catList.breeds == null ||
      catList.breeds.length == 0) {
    return Container();
  }

这会返回一个空的Container,如果API请求返回的数据为空的话。在height参数后面添加如下的代码:

// 1
decoration: BoxDecoration(image: DecorationImage(
  // 2
  image: NetworkImage(catList.breeds[0].url),fit: BoxFit.contain,
)),

解释如下:

  1. BoxDecoration,让你可以在Box里画一张图
  2. NetworkImage,可以通过url加载一张图片

注意,你是要用一个装饰器来显示一张图片,你什么都不用做。只要把url放进NetworkImage里就可以,很酷对吧。

运行代码,你会看到这样的界面:

阅读 1k

full stack dev stills
back-end: Nodejs front-end: iOS, Android, Js

时不我待

837 声望
1.9k 粉丝
0 条评论
你知道吗?

时不我待

837 声望
1.9k 粉丝
宣传栏