看到有同学想要代码,这个在原文中都有。我也正在开发一个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'),
),
),
),
);
}),
);
}
}
解释:
- 构建一个
CatBreedsPage
- 在
AppBar
里设置title
字段的值 - 把
ListView.builder
作为字段的方法 -
count
设置为0,毕竟现在还没有任何的数据 - 使用
Navigator
类跳转到CatInfo
详情页 - 创建一个
Card
,里面放一个Padding
- 添加一个包含title和descriptin的
ListTile
。
使用REST API
请求REST API的时候有不同的method,一般是GET:用来获取数据,也有POST来保存数据,PATCH和PUT用来更新数据。另外还有个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);
}
}
}
解释如下:
- 引入HTTP库
-
Network
类有一个接受一个字符串为参数的构造函数 - 包含了一个异步方法
getData()
- 使用HTTP GET method请求url,并等待返回
- 检查状态码,如果是200那么返回是OK的,否则就是一个Error
- 返回结果
理解JSON
JSON大多数REST API返回的数据的格式。另外一个通用格式是XML。
JSON实例:
{
"user": {
"name": "Kevin Moore",
"occupation": "Programmer"
}
}
上面的例子是从一个{大括号开始,说明是一个对象数据。JSON也可以是一个数组,这时候一般是[开头。JSON格式不能出错,也就是又开始就需要有结束,当它是一个对象的时候就需要有结束的大括号}。
在服务端返回了一个JSON串之后,你可以:
- 从字符串里获得key/value对
- 把字符串转化为一个dart的
Map
对象,再从里面拿到key/value对。 - 把字符串转化为一个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;
}
}
解释如下:
- API的url字符串,获取喵星人列表
- 同样是url串,查找喵星人的图
- 获取一个breed ID
- 存放你的key的字符串
- 获取列表的方法
getCatBreeds()
-
Network
类,需要传入API的url和你的key组成的字符串 - 获取某个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
用来获取喵种的图片,name
和description
用来在列表的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里是不需要用到temperament
和life_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
类里添加fromJson
和toJson
两个方法:
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);
});
解释如下:
- 使用
json.decode(result)
从JSON串得到一个map - 调用
setState
来重绘Widget,因为数据已经发生了变化 - 使用
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
里的title
、subTitle
替换为如下的代码:
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);
这样就把点击那行的id
和name
都传到了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,
)),
解释如下:
- BoxDecoration,让你可以在Box里画一张图
- NetworkImage,可以通过url加载一张图片
注意,你是要用一个装饰器来显示一张图片,你什么都不用做。只要把url放进NetworkImage
里就可以,很酷对吧。
运行代码,你会看到这样的界面:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。