课程名称:Flutter从入门到进阶 实战携程网App 一网打尽核心技术
课程章节:Flutter进阶提升:网络编程与数据存储技术
课程讲师:CrazyCodeBoy
在 Flutter 中可以借助 Future 实现异步操作。
Future 是个泛型类,可以指定类型。如果没有指定相应类型的话,则Future会使用动态的推导类型。Future<T> 类,表示一个T类型的异步操作结果。如果异步操作不需要结果,则类型为 Future<void>。
Future使用工厂构造函数来创建实例,构成构造函数具有以下特点:
Future 有两种状态:
await和 async 是 Dart支持异步的两个关键字:
示例代码:
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.then() 接收两个函数类型的参数,第一个参数是异步执行成功的结果回调,第二个参数onError表示异步执行出现异常,返回一个Future对象。
Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
捕捉 Future 的错误的回调,并且返回一个 Future 对象。
Future.catchError()回调只处理原始 Future 抛出的错误,不能处理回调函数抛出的错误,此时可以使用Future.onError()。如果catchError()与onError()同时存在,则会只调用onError()。
在 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!'); });
开发中会遇到这样的场景:网络请求A和网络请求B都完成以后,再执行代码C,此时可以使用Future.wait()。
完成一个异步操作可能需要很长的时间,比如:网络请求,但有时我们需要为异步操作设置一个超时时间。
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是一个将异步操作和异步UI更新结合在一起的类,通过FutureBuilder可以将网络请求,数据库读取等的结果更新的页面上。
FutureBuilder 的构造方法:
FutureBuilder({Key key, Future<T> future, T initialData, @required AsyncWidgetBuilder<T> builder })
AsyncWidgetBuilder类型:
typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnapshot<T> snapshot);
其中AsyncWidgetBuilder 函数接受两个参数BuildContext context 与 AsyncSnapshot<T> snapshot,返回一个Widget。
AsyncSnapshot包含异步计算的信息,它具有以下属性:
AsyncSnapshot还具有hasData和hasError属性,以分别检查它是否包含非空数据值或错误值。
使用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 参数。