原文:FutureBuilder
Widget that builds itself based on the snapshot of interaction with a Future.
官方的意思是一个基于与Future交互快照构建自身的小组件。
FutureBuilder用法和实现
构造方法:
1
2
3
4
5
6
7
|
const FutureBuilder({
Key key,
this.future, //获取数据的方法
this.initialData, //初始的默认数据
@required this.builder
}) : assert(builder != null),
super(key: key);
|
主要看builder,这个是我们主要关心的,它是我们构建组件的策略。
接收两个参数:BuildContext context,AsyncSnapshot snapshot。
context就不用解释了,snapshot就是_calculation在时间轴上执行过程的状态快照。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//FutureBuilder控件
new FutureBuilder<String>(
future: _calculation, // 用户定义的需要异步执行的代码,类型为Future<String>或者null的变量或函数
builder: (BuildContext context, AsyncSnapshot<String> snapshot) { //snapshot就是_calculation在时间轴上执行过程的状态快照
switch (snapshot.connectionState) {
case ConnectionState.none: return new Text('Press button to start'); //如果_calculation未执行则提示:请点击开始
case ConnectionState.waiting: return new Text('Awaiting result...'); //如果_calculation正在执行则提示:加载中
default: //如果_calculation执行完毕
if (snapshot.hasError) //若_calculation执行出现异常
return new Text('Error: ${snapshot.error}');
else //若_calculation执行正常完成
return new Text('Result: ${snapshot.data}');
}
},
)
|
FutureBuilder通过子属性future获取用户需要异步处理的代码,用builder回调函数暴露异步执行过程中的快照。我们通过builder的参数snapshot暴露的快照属性,定义好对应状态下的处理代码,即可实现异步执行时的交互逻辑。
看似有些绕口,我们看下面这段代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
import 'dart:async';
import 'package:async/async.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/utils/HttpUtil.dart';
class FutureBuilderPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => FutureBuilderState();
}
class FutureBuilderState extends State<FutureBuilderPage> {
String title = 'FutureBuilder使用';
Future _gerData() async {
var response = HttpUtil()
.get('http://api.douban.com/v2/movie/top250', data: {'count': 15});
return response;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
title = title + '.';
});
},
child: Icon(Icons.title),
),
body: FutureBuilder(
builder: _buildFuture,
future: _gerData(), // 用户定义的需要异步执行的代码,类型为Future<String>或者null的变量或函数
),
);
}
///snapshot就是_calculation在时间轴上执行过程的状态快照
Widget _buildFuture(BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
print('还没有开始网络请求');
return Text('还没有开始网络请求');
case ConnectionState.active:
print('active');
return Text('ConnectionState.active');
case ConnectionState.waiting:
print('waiting');
return Center(
child: CircularProgressIndicator(),
);
case ConnectionState.done:
print('done');
if (snapshot.hasError) return Text('Error: ${snapshot.error}');
return _createListView(context, snapshot);
default:
return null;
}
}
Widget _createListView(BuildContext context, AsyncSnapshot snapshot) {
List movies = snapshot.data['subjects'];
return ListView.builder(
itemBuilder: (context, index) => _itemBuilder(context, index, movies),
itemCount: movies.length * 2,
);
}
Widget _itemBuilder(BuildContext context, int index, movies) {
if (index.isOdd) {
return Divider();
}
index = index ~/ 2;
return ListTile(
title: Text(movies[index]['title']),
leading: Text(movies[index]['year']),
trailing: Text(movies[index]['original_title']),
);
}
}
|
在build方法中,我们返回一个Scaffold,主要的代码在body中,包裹了一个FutureBuilder,我们在它的Builder方法中,对不同的状态返回了不同的控件。
snapshot.connectionState就是异步函数_getData的执行状态,用户通过定义在Connectionstate.none和ConnectionState.waiting状态下,输出一个Text和Center显示并且内置文字CircularProgressIndicator的组件,其意义即:当异步函数_getData未执行时,屏幕正中央显示文字:还没有开始网络请求。和正在执行时,显示一个刷新状态的组件。
当_getData执行完毕时,snaphot.connectionState的值变为ConnectionState.done,此时即可输出根据HTTP请求到的数据对应的ListItem。由于ConnectState.done是除了ConnectionState.node和ConnectionState.waiting以外的唯一值,所以代码中在switch下用default也可以(ConnectionState.active好像整个过程中没有调用)。
由于通过FutureBuilder内的builder()函数即可草所控件的状态和重绘,我们不必通过自己写异步状态的判断和多次使用setState()实现页面上加载中和加载完成显示效果的切换,因为FutureBuilder内部自带执行了setState()的方法。
现在一个FutureBuilder的构建就算完成了。
防止FutureBuilder进行不必要的重绘
如果姿势写一个FutureBuilder,我们就不需要floatingActionButton里面一系列东西,所以这时候就到它的出场了。
代码中意思,每次点击它,就在我们标题后面加一个”.“,看一下效果
确实是改变了标题,但是整个页面也随着setState而进行了不必要的重绘。
即使AppBar和FutureBuilder没有任何关联,每次我们改变它的值(通过调用setState),FuterBuilder都会再次经历整个整个生命周期,它重新拉去future,导致不必要的流量,并再次显示负载,导致糟糕的用户体验。
这个问题以各种方式变现出来。某些情况下,它甚至不像上面的例子那么明显。例如:
- 从当前不在屏幕上的页面生成的网络流量
- 热重装不能正常工作
- 更新某些继承的窗口小部件中的值时,丢失导航器状态
- 等等
如果我们仔细看代码FutureBuilder,我们会发现它是一个StatefulWidget。我们知道,StatefulWidget维护一个长期存在的State对象。这种状态有一些管理其声明周期的方法,就像initState,build和didUpdateWidget。
initState在第一次创建状态对象时只调用一次,build在每次我们需要构建显示的窗口小部件时调用它,didUpdateWidget只要附加此State对象的窗口小部件发生变化时,就会调用此方法。
在FutureBuilder这种情况下,这个方法看起来像这样:
1
2
3
4
5
6
7
8
9
10
11
|
@override
void didUpdateWidget(FutureBuilder<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.future != widget.future) {
if (_activeCallbackIdentity != null) {
_unsubscribe();
_snapshot = _snapshot.inState(ConnectionState.none);
}
_subscribe();
}
}
|
如果在构建时,新窗口小部件与旧窗口小部件不同Future实例,则会重复所有的内容:取消订阅,并再次订阅。