作者 | 弗拉德
来源 | 弗拉德(公众号:fulade_me)

异步请求

在移动开发过程中很多时候我们都需要依赖异步请求数据然后再来刷新UI。在用户打开界面的时候,先给出一个Loading提示,等数据请求完成后,我们再把数据展示在页面上,这是很常见的操作。

异步请求的好处就是不会阻塞主线程,用户虽然在“等”,但是页面不会卡死。
同步请求不适应于这种情况,同步请求会出现页面卡死现象,此时用户不能点击(即使点击也没有效果),体验非常不好。
所以大多数时候我们都是使用异步请求来获取数据

http 库

在Flutter中,我们可以用http库来做网络请求,它支持异步请求,并且有良好的API接口
使用http库的步骤:

  • 在项目中,打开pubspec.yaml文件
  • 找到dependencies字段,在下面添加http: ^0.12.2,其中0.12.2是版本号
  • 然后保存pubspec.yaml 并执行pub get命令把我们要使用的第三方库下载下来

具体 pub get命令的使用在之前的文章有介绍过

2021_02_02_http_request_yaml

然后在要使用http库的文件里面引入头文件

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

发送http请求的代码也比较简单,我们这里以get请求为例

1
http.get("https://cdn.jsdelivr.net/gh/johnson8888/blog_pages/images/request_demo_test.json");

只要传入要请求的地址即可,这里的URL地址是我自己上传的测试文件。

http 异步请求返回结果

前面我知道http库发送请求是支持异步的,那么异步请求的返回结果我们该如何接收呢?

  • 通过then函数获取,在get请求之后我们可以直接跟上then函数来作为回调,在回调内部可以获取到请求的结果
    1
    2
    3
    http.get(getURL).then((value) {
    print(value);
    });
    这样写确实很方便,但当我们的网络请求很多,并且一个网络请求依赖另外一个网络请求的时候,这个时候就会多个回调函数嵌套在一起(又称为回调地狱),代码就会显得很凌乱,很不适合Debug。
  • 使用await来接收异步操作的结果
    1
    var data = await http.get(getURL);
    这样写代码就比上面的代码清爽多了
    但是需要注意的是,如果函数内部有被await修饰的方法,那么函数应该被async来修饰,并且返回值需要被Future修饰,Future是一个延时计算的对象,在被await修饰的函数返回的时候才能拿到Future的具体值。
    示例入下:
    1
    2
    3
    4
    Future<Map> getData() async {
    var data = await http.get(getURL);
    return data.body;
    }

刷新页面

我们前面已经知道:调用setState()函数可以刷新页面,所以在http请求之后我们调用setState()函数即可刷新页面

1
2
3
4
5
6
7
http.get(getURL).then((value) {
print(value);
var data = jsonDecode(data.body);
setState(() {
/// 此处执行刷新页面的代码
});
});

使用FutureBuilder来刷新页面

setState()固然是可以刷新页面,但是当我们页面内有多个网络请求的时候,就会不停的调用setState()来全量刷新页面,显然这就有点冗余。
Flutter为我们提供了更好的方式来实现获取数据并且刷新UI的操作,那就是FutureBuilder
来看它的初始化方法

1
2
3
4
5
6
7
8
9
10
const FutureBuilder({
/// key
Key key,
/// 异步的操作
this.future,
/// 初始化数据
this.initialData,
/// 构建UI的函数
@required this.builder,
})

由构造函数可见,我们需要传入future参数,也就是我们的耗时操作函数,还需要传入builder函数
builder方法里可以捕捉到两个参数BuildContext contextAsyncSnapshot snap
其中snap的属性会携带future的耗时函数的返回值,也就是说:在耗时操作函数返回结果之后,我们可以在builder方法内获取到这一返回值。
所以上面的请求我们也可以这么来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
FutureBuilder(
future: getData(),
builder: (BuildContext context, AsyncSnapshot snap) {
/// 如果没有数据 我们就显示loading页面
if (snap.hasData == false) {
return CircularProgressIndicator();
} else {
/// 如果获取到了数据 我们就初始化一个 ListView来展示获取到的数据
var dataSource = snap.data["tracks"];
return ListView.builder(
itemCount: dataSource.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(dataSource[index]["title"]),
subtitle: Text(dataSource[index]["cover"]),
);
},
);
}
},
),

在获取到数据之后,我们通过ListView.builder来构建一个ListView并返回,此时就完成了刷新UI的工作

我这里写的比较简单,只是用hasData来做为判断的依据
其实还有更优雅的做法:使用snap的另一个属性connectionState

1
2
3
4
5
6
7
8
9
10
enum ConnectionState {
/// 没有异步任务,
none,
/// 异步任务正在等待
waiting,
/// 异步任务正在执行 或者 数据正在传输
active,
/// 异步任务已经终止.
done,
}

我们也可以在connectionStatedone的时候在来判断是否存在数据
如果存在就展示数据!

想体验以上的示例的运行效果,可以到我的Github仓库项目flutter_app->lib->routes->http_page.dart查看,并且可以下载下来运行并体验。


公众号