为了账号安全,请及时绑定邮箱和手机立即绑定

Flutter FutureBuilder 异步UI神器

标签:
CSS3

一般程序员都会了解,类似于 IO、网络请求等都应该是异步的。

在Dart中,我们使用 Future 来管理,这样就不用担心线程或者死锁的问题。

那么当 Flutter 涉及到 Future 的时候,widget 该如何去构建呢?

在网络请求 开始前、请求中、请求完成或失败,我们应该如何去管理我们的UI?

为此,Flutter 推出 FutureBuilder。

什么是FutureBuilder

先看文档:

Widget that builds itself based on the latest snapshot of interaction with a Future.

翻译过来说就是 FutureBuilder 是基于 Future 快照来构建自身的一个组件。

快照是啥玩意?个人理解就是这个 Future 目前的信息。

这个信息里面包括:目前的状态、所携带的数据等等

如何使用

先看看 FutureBuilder 是个啥, 点开源码:

1.  `class  FutureBuilder<T>  extends  StatefulWidget  {`
    
2.   `const  FutureBuilder({`
    
3.   `Key key,`
    
4.   `this.future,`
    
5.   `this.initialData,`
    
6.   `@required  this.builder,`
    
7.   `})  :  assert(builder !=  null),`
    
8.   `super(key: key);`
    
9.  `}`
    
10.    
    
11.    
    
12.  `final  AsyncWidgetBuilder<T> builder;`

看出来是个有状态的小部件,找到 State 的 build 方法:

Widgetbuild(BuildContextcontext)=>widget.builder(context,_snapshot);

build 方法直接返回了一个 widget 的 builder。

那我们继续,打开官网看官方Demo怎么写的:

1.  `FutureBuilder<String>(`
    
2.   `future: _calculation,  // a previously-obtained Future<String> or null`
    
3.   `builder:  (BuildContext context,  AsyncSnapshot<String> snapshot)  {`
    
4.   `switch  (snapshot.connectionState)  {`
    
5.   `case  ConnectionState.none:`
    
6.   `return  Text('Press button to start.');`
    
7.   `case  ConnectionState.active:`
    
8.   `case  ConnectionState.waiting:`
    
9.   `return  Text('Awaiting result...');`
    
10.   `case  ConnectionState.done:`
    
11.   `if  (snapshot.hasError)`
    
12.   `return  Text('Error: ${snapshot.error}');`
    
13.   `return  Text('Result: ${snapshot.data}');`
    
14.   `}`
    
15.   `return  null;  // unreachable`
    
16.   `},`
    
17.  `)`
    

可以看到 FutureBuilder 定义了一个泛型,这个泛型是用来获取快照中数据时用的。

FlutureBuilder 有两个参数:

future:这个参数需要一个 Future 对象,类似于 网络请求、IO

builder:这个参数需返回一个 widget,我们可以看到 demo 中根据现在快照不同的连接状态返回不同的 widget。

我们再来看一下 snapshot.connectionState都有哪些值:

现在了解了之后我们就可以有想法了。

我们在打开一个页面的时候肯定会有网络请求,这个时候要显示 loading 之类的,我们就可以利用当前快照的状态来返回不同的 widget,比如这样:

图片描述

首先看build代码:

1.  `@override`
    
2.  `Widget build(BuildContext context)  {`
    
3.   `return  Scaffold(`
    
4.   `appBar:  AppBar(`
    
5.   `title:  Text('FutureBuilderPage'),`
    
6.   `),`
    
7.   `body:  FutureBuilder(`
    
8.   `builder:  (context, snapshot)  {`
    
9.   `switch  (snapshot.connectionState)  {`
    
10.   `case  ConnectionState.none:`
    
11.   `case  ConnectionState.active:`
    
12.   `case  ConnectionState.waiting:`
    
13.   `print('waiting');`
    
14.   `return  Center(child:  CupertinoActivityIndicator());`
    
15.   `case  ConnectionState.done:`
    
16.   `print('done');`
    
17.   `if  (snapshot.hasError)  {`
    
18.   `return  Center(`
    
19.   `child:  Text('网络请求出错'),`
    
20.   `);`
    
21.   `}`
    
22.    
    
23.   `return generateListView();`
    
24.   `}`
    
25.   `return  null;`
    
26.   `},`
    
27.   `future: _future,`
    
28.   `),`
    
29.   `);`
    
30.  `}`
    

Scaffold 的 body 直接返回一个 FutureBuilder,根据不同状态来返回了不同的 widget。

**这里需要注意的一点是:**我们知道 StatefulWidget会长时间维护一个 State,当有变动的时候会调用 didUpdateWidget方法,就要重新build了。所以 FutureBuilder的官方文档上有这么一段文字:

> The future must have been obtained earlier, e.g. during State.initState, `State.didUpdateConfig`, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.buildmethod call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
> 
> A general guideline is to assume that every `build` method could get called every frame, and to treat omitted calls as an optimization.

大致意思就是说 future 这个参数建议在 initState() 里初始化,不要在 build 方法里初始化,这样的话会一直 rebuild。

为什么呢,我们查看 didUpdateWidget 源码:

1.  `@override`
    
2.  `void didUpdateWidget(FutureBuilder<T> oldWidget)  {`
    
3.   `super.didUpdateWidget(oldWidget);`
    
4.   `if  (oldWidget.future != widget.future)  {`
    
5.   `if  (_activeCallbackIdentity !=  null)  {`
    
6.   `_unsubscribe();`
    
7.   `_snapshot = _snapshot.inState(ConnectionState.none);`
    
8.   `}`
    
9.   `_subscribe();`
    
10.   `}`
    
11.  `}`
    

可以看出来这里是判断了 future 这个字段,所以我们一定不要在 build 方法里初始化 future 参数!

所以,我们在 initState()方法里初始化:

1.  `Future _future;`
    
2.  `Dio _dio;`
    
3.  `int date =  20190523;`
    
4.  `List<Stories> _newsData =  [];`
    
5.    
    
6.  `@override`
    
7.  `void initState()  {`
    
8.   `super.initState();`
    
9.   `_dio =  Dio();`
    
10.   `_future = getNewsList();`
    
11.  `}`
    
12.    
    
13.  `// 获取知乎每天的新闻,数据获取成功后 setState来刷新数据`
    
14.  `Future getNewsList()  async  {`
    
15.   `var response =`
    
16.   `await _dio.get('https://news-at.zhihu.com/api/4/news/before/$date');`
    
17.   `setState(()  {`
    
18.   `_newsData.addAll(ZhiHuNews.fromJson(response.data)._stories);`
    
19.   `});`
    
20.  `}`
    

generateListView 方法就不放了,就是基本的 ListView.builder()。

这样我们就完成了上图的效果,在网络请求的时候加载小菊花,请求成功加载出 ListView.

小结

可以看得出来 FutureBuilder 确实是非常方便,而且我们可以自己封装几个控件,后面用的时候就会更加完美。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
移动开发工程师
手记
粉丝
30
获赞与收藏
45

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消