Flutter FutureBuilder 异步UI神器

一般程序员都会了解,类似于 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 是个啥, 点开源码:

class FutureBuilder<T> extends StatefulWidget {
  const FutureBuilder({
    Key key,
    this.future,
    this.initialData,
    @required this.builder,
  }) : assert(builder != null),
       super(key: key);
}
  

final AsyncWidgetBuilder<T> builder;
复制代码

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

Widget build(BuildContext context) => widget.builder(context, _snapshot);

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

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

FutureBuilder<String>(
  future: _calculation, // a previously-obtained Future<String> or null
  builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
    switch (snapshot.connectionState) {
      case ConnectionState.none:
        return Text('Press button to start.');
      case ConnectionState.active:
      case ConnectionState.waiting:
        return Text('Awaiting result...');
      case ConnectionState.done:
        if (snapshot.hasError)
          return Text('Error: ${snapshot.error}');
        return Text('Result: ${snapshot.data}');
    }
    return null; // unreachable
  },
)
复制代码

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

FlutureBuilder 有两个参数:

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

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

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

ConnectionState当前没有连接到任何的异步任务
ConnectionState.none当前没有连接到任何的异步任务
ConnectionState.waiting连接到异步任务并等待进行交互
ConnectionState.active连接到异步任务并开始交互
ConnectionState.done异步任务中止

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

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

首先看build代码:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('FutureBuilderPage'),
    ),
    body: FutureBuilder(
      builder: (context, snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.none:
          case ConnectionState.active:
          case ConnectionState.waiting:
            print('waiting');
            return Center(child: CupertinoActivityIndicator());
          case ConnectionState.done:
            print('done');
            if (snapshot.hasError) {
              return Center(
                child: Text('网络请求出错'),
              );
            }

            return generateListView();
        }
        return null;
      },
      future: _future,
    ),
  );
}
复制代码

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 源码:

@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 这个字段,所以我们一定不要在 build 方法里初始化 future 参数!

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

Future _future;
Dio _dio;
int date = 20190523;
List<Stories> _newsData = [];

@override
void initState() {
  super.initState();
  _dio = Dio();
  _future = getNewsList();
}

// 获取知乎每天的新闻,数据获取成功后 setState来刷新数据
Future getNewsList() async {
  var response =
    await _dio.get('https://news-at.zhihu.com/api/4/news/before/$date');
  setState(() {
    _newsData.addAll(ZhiHuNews.fromJson(response.data)._stories);
  });
}
复制代码

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

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

小结

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

如想获取源码请移步本人Github:github.com/wanglu1209/…

转载于:https://juejin.im/post/5ce73d33f265da1bc8540261

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值