课程名称:Flutter从入门到进阶 实战携程网App 一网打尽核心技术
课程章节:Flutter进阶提升:网络编程与数据存储技术
课程讲师:CrazyCodeBoy
课程内容
Future
Future介绍
在 Flutter 中可以借助 Future 实现异步操作。
Future 是个泛型类,可以指定类型。如果没有指定相应类型的话,则Future会使用动态的推导类型。Future<T> 类,表示一个T类型的异步操作结果。如果异步操作不需要结果,则类型为 Future<void>。
Future使用工厂构造函数来创建实例,构成构造函数具有以下特点:
- 在 Dart 中工厂构造函数的关键字是 factory;
- 与一般的构造函数不同,工厂构造函数不会自动生成实例,而是通过代码来决定返回的实例;
- 在构造方法前加上factory 关键字后就变成了工厂构造函数;
Future 有两种状态:
- pending,表示 Future异步操作正在执行,此时还没有执行结果;
- completed,执行结束,返回值有两种情况,异步执行的结果和失败的状态。
await 和 async与 Future 配合使用
await和 async 是 Dart支持异步的两个关键字:
- await,后面会跟着一个 Future,表示等待该异步任务完成后才会继续往下执行。await只能出现在异步函数内部,能够让开发人员可以像写同步代码那样来执行异步任务,而不使用回调的方式。
- async,修饰的函数是异步的,修饰的函数会返回一个 Future 对象。
示例代码:
test() async {
int result = await Future.delayed(Duration(milliseconds: 2000), () {
return Future.value(123);
});
print('t3:' + DateTime.now().toString());
print(result);
}
main() {
print('t1:' + DateTime.now().toString());
test();
print('t2:' + DateTime.now().toString());
}
Future 中的其它方法
Future.then()
Future.then() 接收两个函数类型的参数,第一个参数是异步执行成功的结果回调,第二个参数onError表示异步执行出现异常,返回一个Future对象。
Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
Future.catchError()
捕捉 Future 的错误的回调,并且返回一个 Future 对象。
Future.onError()
Future.catchError()回调只处理原始 Future 抛出的错误,不能处理回调函数抛出的错误,此时可以使用Future.onError()。如果catchError()与onError()同时存在,则会只调用onError()。
future.whenComplete()
在 Future 完成之后总是会调用,不管是错误导致的完成还是正常执行完毕,并且返回一个 Future 对象。then().catchError()的模式类似于try-catch,try-catch有个finally代码块,而Future.whenComplete() 相当于Future中的finally。
示例代码:
void main() {
var random = Random();
Future.delayed(Duration(seconds: 3), () {
if (random.nextBool()) {
return 100;
} else {
throw 'boom!';
}
}).then(print).catchError(print).whenComplete(() {
print('done!');
});
Future.wait()
开发中会遇到这样的场景:网络请求A和网络请求B都完成以后,再执行代码C,此时可以使用Future.wait()。
- 如果所有 Future 都有正常结果返回。则Future的返回结果是所有指定Future的结果的集合;
- 如果其中一个或者几个 Future 发生错误,产生了error。则 Future 的返回结果是第一个发生错误的 Future 的值。
future.timeout()
完成一个异步操作可能需要很长的时间,比如:网络请求,但有时我们需要为异步操作设置一个超时时间。
void main() {
new Future.delayed(new Duration(seconds: 3), () {
return 1;
}).timeout(new Duration(seconds: 2)).then(print).catchError(print);
}
运行上述代码会看到:
TimeoutException after 0:00:02.000000: Future not completed。
FutureBuilder
FutureBuilder是一个将异步操作和异步UI更新结合在一起的类,通过FutureBuilder可以将网络请求,数据库读取等的结果更新的页面上。
FutureBuilder的构造方法
FutureBuilder 的构造方法:
FutureBuilder({Key key, Future<T> future, T initialData, @required AsyncWidgetBuilder<T> builder })
- future,是Future对象表示此构建器当前连接的异步计算;
- initialData,表示一个非空的Future完成前的初始化数据;
- builder,是AsyncWidgetBuilder类型的回调函数,是一个基于异步交互构建widget的函数;
AsyncWidgetBuilder类型:
typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnapshot<T> snapshot);
其中AsyncWidgetBuilder 函数接受两个参数BuildContext context 与 AsyncSnapshot<T> snapshot,返回一个Widget。
AsyncSnapshot包含异步计算的信息,它具有以下属性:
- connectionState,枚举类型,表示与异步计算的连接状态,有四个值:none(当前没有连接到任何的异步任务),waiting(连接到异步任务并等待进行交互),active(连接到异步任务并开始交互)和done(异步任务中止);
- data,异步计算接收的最新数据;
- error,异步计算接收的最新错误对象;
AsyncSnapshot还具有hasData和hasError属性,以分别检查它是否包含非空数据值或错误值。
FutureBuilder的使用
使用FutureBuilder的基本模式,即在创建新的FutureBuilder对象时,将Future对象作为要处理的异步计算传递。 在构建器函数中,检查connectionState的值,并使用AsyncSnapshot中的数据或错误返回不同的窗口小部件。
class _MyAppState extends State<MyApp> {
String showResult = '';
Future<CommonModel> fetchPost() async {
final response = await http
.get('https://www.devio.org/io/flutter_app/json/test_common_model.json');
Utf8Decoder utf8decoder = Utf8Decoder(); //fix 中文乱码
var result = json.decode(utf8decoder.convert(response.bodyBytes));
return CommonModel.fromJson(result);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Future与FutureBuilder实用技巧'),
),
body: FutureBuilder<CommonModel>(
future: fetchPost(),
builder:
(BuildContext context, AsyncSnapshot<CommonModel> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return new Text('Input a URL to start');
case ConnectionState.waiting:
return new Center(child: new CircularProgressIndicator());
case ConnectionState.active:
return new Text('');
case ConnectionState.done:
if (snapshot.hasError) {
return new Text(
'${snapshot.error}',
style: TextStyle(color: Colors.red),
);
} else {
return new Column(children: <Widget>[
Text('icon:${snapshot.data.icon}'),
Text('statusBarColor:${snapshot.data.statusBarColor}'),
Text('title:${snapshot.data.title}'),
Text('url:${snapshot.data.url}')
]);
}
}
}),
),
);
}
}
class CommonModel {
final String icon;
final String title;
final String url;
final String statusBarColor;
final bool hideAppBar;
CommonModel(
{this.icon, this.title, this.url, this.statusBarColor, this.hideAppBar});
factory CommonModel.fromJson(Map<String, dynamic> json) {
return CommonModel(
icon: json['icon'],
title: json['title'],
url: json['url'],
statusBarColor: json['statusBarColor'],
hideAppBar: json['hideAppBar'],
);
}
}
课程总结
由于StatefulWidget 会维护一个 State,当State有变动的时候会调用 didUpdateWidget()方法,重新build()。
在使用FutureBuilder时,future这个参数建议在 initState() 里初始化,不要在 build() 方法里初始化,否则会一直 rebuild()。
didUpdateWidget() 方法的源码:
@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 这个字段,然后会重复调用 inState(),重复进行rebuild()。所以一定不要在 build 方法里初始化 future 参数。