Flutter系列文章目录导读:
(一)Flutter学习之Dart变量和类型系统
(二)Flutter学习之Dart展开操作符 和 Control Flow Collections
(三)Flutter学习之Dart函数
(四)Flutter学习之Dart操作符、控制流和异常处理
(五)Flutter学习之Dart面向对象
(六)Flutter学习之Dart异步操作详解
(七)Flutter 学习之开发环境搭建
(八)Flutter 和 Native 之间的通信详解
(九)Android 项目集成 Flutter 模块
(十)Flutter FutureBuilder 优雅构建异步UI
更新中…
在实际开发中, 一般在展示列表内容之前需要先展示一个 loading
表示正在加载, 当加载成功后展示列表内容, 加载失败展示失败的界面
所以, 这样一个需求就涉及到了三种情况:
从前面的文章(《(二)Flutter 学习之 Dart 展开操作符和 Control Flow Collections》)[https://chiclaim.blog.csdn.net/article/details/94617048] 我们知道 Flutter UI
是 声明式UI
不同UI的切换时通过 setState
来重新构建的. 那么上面的三种情况UI 我们需要通过 if else
来判断到底展示那种界面.
例如下面的伪代码:
@override
Widget build(BuildContext context) {
if(loading) { // 正在加载
return Text("Loading...");
} else if(isError) { // 加载出错
return Text("Error...");
} else { // 展示列表内容
return ListView(...)
}
}
这种方式虽然也能实现上面的需求, 但是不利于代码的维护, 需要维护很多变量, 很不优雅.
FutureBuilder
的用法很简单, 主要涉及两个参数:
future
指定异步任务, 交给 FutureBuilder
管理builder
根据异步任务的状态来构建不同的 Widget
, 类似上面的 if/else
FutureBuilder
中的异步任务状态有:
状态 | 描述 |
---|---|
none | 没有连接到任何异步任务 |
waiting | 已连接到异步任务等待被交互 |
active | 已连接到一个已激活的异步任务 |
done | 已连接到一个已结束的异步任务 |
我们可以使用 FutureBuilder
改造上面的案例, 代码如下所示:
FutureBuilder(
future: _loadList(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
// 显示正在加载
return createLoadingWidget();
case ConnectionState.done:
// 提示错误信息
if (snapshot.hasError) {
return createErrorWidget(snapshot.error.toString());
}
// 展示列表内容
return ListView.separated(
itemCount: snapshot.data,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text(index.toString()));
},
separatorBuilder: (BuildContext context, int index) {
return divider;
},
);
default:
return Text("unknown state");
)
需要注意的是, 上面的代码界面每次被重建的时候都会执行 loadList 操作.
但是有的时候并不是界面发生变化的时候都需要去重新执行 future
, 例如界面一个 Tab + ListView(文章分类+文章列表)
, 文章分类是需要先加载, 那么文章分类的异步任务就是 future
, 加载成功分类后, 才能去加载文章列表, 列表加载成功界面会重新构建, 这个时候是不应该再次加载文章分类的(future)
这个时候需要在把 future
变量作为成员变量, 在 initState
中初始化, 然后再传递给 future
参数, 如:
Future _future;
@override
void initState() {
_future = _loadList();
super.initState();
}
FutureBuilder(
future: _future,
...
)
运行效果如下图所示:
FutureBuilder
继承了 StatefulWidget
, 所以主要代码都集中在 State
中
class _FutureBuilderState extends State> {
Object _activeCallbackIdentity;
AsyncSnapshot _snapshot;
@override
void initState() {
super.initState();
// 初始化异步快照, 初始状态为 none
_snapshot = AsyncSnapshot.withData(ConnectionState.none, widget.initialData);
// 关联异步任务
_subscribe();
}
// 页面发生变化判断老的widget的 future 和新widget future 是否是同一个对象
// 如果是同一个对象则不会执行异步任务, 否则会重新执行异步任务
@override
void didUpdateWidget(FutureBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.future != widget.future) {
if (_activeCallbackIdentity != null) {
_unsubscribe();
_snapshot = _snapshot.inState(ConnectionState.none);
}
_subscribe();
}
}
// 执行外部传入的 builder 回调
// widget 就是 State 对应的 FutureBuilder(StatefulWidget)
@override
Widget build(BuildContext context) => widget.builder(context, _snapshot);
@override
void dispose() {
_unsubscribe();
super.dispose();
}
void _subscribe() {
if (widget.future != null) {
final Object callbackIdentity = Object();
_activeCallbackIdentity = callbackIdentity;
// 开始执行异步任务
widget.future.then((T data) {
if (_activeCallbackIdentity == callbackIdentity) {
// 刷新界面
setState(() {
// 组件异步快照数据
_snapshot = AsyncSnapshot.withData(ConnectionState.done, data);
});
}
}, onError: (Object error) {
// 执行异步任务发生异常
if (_activeCallbackIdentity == callbackIdentity) {
setState(() {
_snapshot = AsyncSnapshot.withError(ConnectionState.done, error);
});
}
});
// 将异步任务状态设置为 waiting
_snapshot = _snapshot.inState(ConnectionState.waiting);
}
}
void _unsubscribe() {
_activeCallbackIdentity = null;
}
除了 FutureBuilder
可以优雅构建异步UI, StreamBuilder
也可以实现, 但是一般的异步任务 UI 展示并不是一个 Stream
流的形式, 更像是一次性的逻辑处理, 只要成功后, 一般不需要更新, 所以使用 FutureBuilder
就完全够了. 实际开发中根据情况来选择. StreamBuilder
的功能更加强大, 后期如果往 stream
中发送数据 UI 界面也跟着发生变化 如:
StreamBuilder(
// 这个是stream 而不是 future
stream: _streamController.stream,
initialData: _counter,
builder: (BuildContext context, AsyncSnapshot snapshot){
// 接收到 controller 发送给 stream 的数据
return Text('${snapshot.data}');
}
),
)
我们可以通过_streamController
发送数据, 然后会自动调用 StreamBuilder builder
回调, 从而刷新 Widget
_streamController.sink.add(++_counter);
当然也可以不通过 StreamController
来提供 stream
, 也可以创建一个函数返回 stream
, 具体如何创建可以查看我之前的文章 《(六)Flutter 学习之 Dart 异步操作详解》
与此同时,我编写了一份: 超详细的 Android 程序员所需要的技术栈思维导图。
如果有需要可以移步到我的 GitHub -> AndroidAll,里面包含了最全的目录和对应知识点链接,帮你扫除 Android 知识点盲区。 由于篇幅原因只展示了 Android 思维导图: