一、前言:
1.先简单说下源码之间
吧
- 1 】:
源码之间
是张风捷特烈
在bilibili
的直播间,版权所有。 - 2 】: 源码之间直播和产出的
所有视频资源
都将是免费的
,允许被录制
、加工
和随意传播
。 - 3 】:
禁止使用
源码之间的视频资源做任何盈利行为
的是事,违者必究。 - 4 】: 源码之间的直播内容主要是
源码的分析
,也可能是分享和研究
某一编程问题。
FutureBuilder源码分析: 录播视屏: www.bilibili.com/video/BV1We…
示例demo的代码贴在文尾,可以自己跑跑,调试看看。
2.示例demo效果
主要就是请求网络api,返回数据,展业界面。根据不同的状态显示不同的界面。
加载中 | 加载完成 | 加载失败 |
---|---|---|
|
|
|
一、示例demo详述:
1.关于异步请求
FutureBuilder需要一个异步任务作为构造入参
通过wanandroid的开发api进行文章列表的获取,Api.fetch(int page)
class Api {
static Future<List<Article>> fetch(int page) async {
var url = 'https://www.wanandroid.com/article/list/$page/json';
var rep = await http.get(url);
await Future.delayed(Duration(seconds: 3));
if (rep.statusCode == 200) {
var datas = json.decode(rep.body)['data']['datas'] as List;
return datas.map(Article.formMap).toList();
}
return null;
}
}
复制代码
文章用三个字段表示
class Article {
final String title;
final String url;
final String time;
const Article({this.title, this.time, this.url});
static Article formMap(dynamic json) {
if (json == null) return null;
return Article(
title: json['title'] ?? '未知',
url: json['link'] ?? '',
time: json['niceDate'] ?? '',
);
}
@override
String toString() {
return 'Article{title: $title, url: $url, time: $time}';
}
}
复制代码
2. FutureBuilder的使用
先定义异步任务和当前页码,在使用
FutureBuilder
进行构造组件。全代码见文尾。
class _HomePageState extends State<HomePage> {
Future<List<Article>> _articles;
var _page = 0;
@override
void initState() {
_articles = Api.fetch(_page);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
//略...
),
body: FutureBuilder<List<Article>>(
future: _articles,
builder: _builderList,
),
);
}
复制代码
二、FutureBuilder源码分析
1. FutureBuilder组件类
- FutureBuilder是一个具有泛型T的类,T代表异步的数据类型,这里也就是
List<Article>
- FutureBuilder是一个StatefulWidget,主要有三个成员变量:
1】.
future
:Future<T> 类型
----待执行的异步任务
2】.builder
:AsyncWidgetBuilder<T>类型
----异步组件构造器
3】.initialData
:T 类型
----初始数据
class FutureBuilder<T> extends StatefulWidget {
/// Creates a widget that builds itself based on the latest snapshot of
/// interaction with a [Future].
///
/// The [builder] must not be null.
const FutureBuilder({
Key key,
this.future,
this.initialData,
@required this.builder,
}) : assert(builder != null),
super(key: key);
final Future<T> future;
final AsyncWidgetBuilder<T> builder;
final T initialData;
复制代码
2. _FutureBuilderState状态类
- StatefulWidget主要依赖State进行构建组件,所以这里重要的是
_FutureBuilderState
其中有两个成员变量
_activeCallbackIdentity
和_snapshot
class _FutureBuilderState<T> extends State<FutureBuilder<T>> {
Object _activeCallbackIdentity;
AsyncSnapshot<T> _snapshot;
复制代码
在
_FutureBuilderState#initState
中对_snapshot
进行初始化
@override
void initState() {
super.initState();
_snapshot = AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData);
_subscribe();
}
复制代码
3. AsyncSnapshot状态量类
所以先看一下
_snapshot
对象所对应的AsyncSnapshot<T>
类
它核心是三个成员变量,记录状态、数据和异常情况
并且提供一些命名构造
方便创建对象和一些get方法
方便使用
final ConnectionState connectionState; # 连接状态
final T data; # 数据
final Object error; # 异常
复制代码
简单来说,就是装着三个东西的瓶子。
- 还有个比较重要的是连接的状态
ConnectionState
enum ConnectionState {
none, # 初始化时最初
waiting, # 刚开始执行异步任务时,等待期
active, # Stream中激活但未结束
done, # 结束
}
复制代码
现在回看_FutureBuilderState#initState
中对_snapshot
进行初始化时:连接状态是none,数据是提供的初始数据,没有则为null
@override
void initState() {
super.initState();
_snapshot = AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData);
_subscribe();
}
复制代码
4. FutureBuilder的核心逻辑
- _snapshot初始化完成,然后执行
_subscribe()
这是FutureBuilder的灵魂
如果
widget.future
非空,会创建callbackIdentity
标识,然后启动异步任务
接着将_snapshot的状态设为ConnectionState.waiting
void _subscribe() {
if (widget.future != null) {
final Object callbackIdentity = Object();
_activeCallbackIdentity = callbackIdentity;
widget.future.then<void>((T data) {
//昝略...
}, onError: (Object error) {
//昝略...
});
_snapshot = _snapshot.inState(ConnectionState.waiting);
}
}
复制代码
- initState完成,之后会调用State#build
这里是用来外部传的builder方法来创建组件,其中会回调
_snapshot
给外界使用
这时_snapshot的状态是waiting
;
@override
Widget build(BuildContext context) => widget.builder(context, _snapshot);
复制代码
在外界处理通过
_builderList
方法创建组件
body: FutureBuilder<List<Article>>(
future: _articles,
builder: _builderList,
),
复制代码
根据回调的
snapshot
,你可以决定返回的界面
比如现在是ConnectionState.waiting
,就可以返回loading组件
Widget _builderList(
BuildContext context, AsyncSnapshot<List<Article>> snapshot) {
switch (snapshot.connectionState) {
//...其他昝略
case ConnectionState.waiting: //<--- 当前waiting
print('-------ConnectionState.waiting---------');
return _buildLoading();
break;
}
}
复制代码
- 接下来异步事件完成后,会回调then中的函数,也就是源码中的这里
- 可以看出回调中会将异步返回的数据放在
_snapshot
这个瓶子里,并setState
- 这样
_snapshot
更新后,会重新执行build方法,又会回调外界的_builderList
void _subscribe() {
if (widget.future != null) {
final Object callbackIdentity = Object();
_activeCallbackIdentity = callbackIdentity;
widget.future.then<void>((T data) {
if (_activeCallbackIdentity == callbackIdentity) {
setState(() {
_snapshot = AsyncSnapshot<T>.withData(ConnectionState.done, data);
});
}
}, onError: (Object error) {
if (_activeCallbackIdentity == callbackIdentity) {
setState(() {
_snapshot = AsyncSnapshot<T>.withError(ConnectionState.done, error);
});
}
});
_snapshot = _snapshot.inState(ConnectionState.waiting);
}
}
复制代码
这样就会跳到
ConnectionState.done
而返回列表组件
当发生异常snapshot.hasError会为true,这样可以构建错误界面
Widget _builderList( BuildContext context, AsyncSnapshot<List<Article>> snapshot) {
if (snapshot.hasError) {
return _buildError(snapshot.error);
}
switch (snapshot.connectionState) {
//...其他昝略
case ConnectionState.done:
print('-------ConnectionState.done---${snapshot.hasData}------'
if (snapshot.hasData) {
return _buildList(snapshot.data);
}
break;
}
}
Widget _buildList(List<Article> data) {
return CupertinoScrollbar(
child: ListView.separated(
separatorBuilder: (_, index) => Divider(),
itemCount: data.length,
itemBuilder: (_, index) => ListTile(
title: Text(
data[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
data[index].url,
style: TextStyle(fontSize: 10),
),
trailing: Text(data[index].time.split(' ')[0]),
)),
);
}
复制代码
所以FutureBuilder的能力就是
根据异步任务的执行情况,向外界暴露出状态方便构建不同的界面
。
5. 父组件刷新时的_FutureBuilderState的行为
- 在点击加号时,更新异步方法,获取下一页数据,然后父组件执行setState
void _doAdd() {
setState(() {
_page++;
_articles = Api.fetch(_page);
});
}
复制代码
此时并不会走State#initState,而是
didUpdateWidget
当两个异步任务不同时,则会先取消之前的,然后再执行_subscribe
之后的事就和上面是一样的了。
@override
void didUpdateWidget(FutureBuilder<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.future != widget.future) {
if (_activeCallbackIdentity != null) {
_unsubscribe();
_snapshot = _snapshot.inState(ConnectionState.none);
}
_subscribe();
}
}
复制代码
取消也很简单,标识置空即可。
void _unsubscribe() {
_activeCallbackIdentity = null;
}
复制代码
FutureBuilder的源码也就这些,看到了也就不是很难。说白了就是在封装一下异步任务执行情况,本质也是靠setState进行更新子组件。
尾声
欢迎Star和关注FlutterUnit 的发展,让我们一起携手,成为Unit一员。
另外本人有一个Flutter微信交流群,欢迎小伙伴加入,共同探讨Flutter的问题,期待与你的交流与切磋。
@张风捷特烈 2020.05.10 未允禁转
我的公众号:编程之王
联系我--邮箱:1981462002@qq.com --微信:zdl1994328
~ END ~
附录: demo 源码
1. api.dart
import 'dart:convert';
/// create by 张风捷特烈 on 2020/5/9
/// contact me by email 1981462002@qq.com
/// 说明:
import 'package:http/http.dart' as http;
class Api {
static Future<List<Article>> fetch(int page) async {
var url = 'https://www.wanandroid.com/article/list/$page/json';
var rep = await http.get(url);
await Future.delayed(Duration(seconds: 3));
if (rep.statusCode == 200) {
var datas = json.decode(rep.body)['data']['datas'] as List;
return datas.map(Article.formMap).toList();
}
return null;
}
}
class Article {
final String title;
final String url;
final String time;
const Article({this.title, this.time, this.url});
static Article formMap(dynamic json) {
if (json == null) return null;
return Article(
title: json['title'] ?? '未知',
url: json['link'] ?? '',
time: json['niceDate'] ?? '',
);
}
@override
String toString() {
return 'Article{title: $title, url: $url, time: $time}';
}
}
复制代码
2. main.dart
import 'package:flutter/cupertino.dart';
/// create by 张风捷特烈 on 2020/5/9
/// contact me by email 1981462002@qq.com
/// 说明:
import 'package:flutter/material.dart';
import 'api.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Future Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage());
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Future<List<Article>> _articles;
var _page = 0;
@override
void initState() {
_articles = Api.fetch(_page);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: _doAdd,
child: Icon(Icons.add),
),
appBar: AppBar(
title: Text('FutureBuilder 当前页 $_page'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.remove),
onPressed: _doMinus,
)
],
),
body: FutureBuilder<List<Article>>(
future: _articles,
builder: _builderList,
),
);
}
Widget _builderList(
BuildContext context, AsyncSnapshot<List<Article>> snapshot) {
if (snapshot.hasError) {
return _buildError(snapshot.error);
}
switch (snapshot.connectionState) {
case ConnectionState.none:
print('-------ConnectionState.none---------');
break;
case ConnectionState.waiting:
print('-------ConnectionState.waiting---------');
return _buildLoading();
break;
case ConnectionState.active:
print('-------ConnectionState.active---------');
break;
case ConnectionState.done:
print('-------ConnectionState.done---${snapshot.hasData}------');
if (snapshot.hasData) {
return _buildList(snapshot.data);
}
break;
}
return Container(
color: Colors.orange,
);
}
Widget _buildLoading() => Center(
child: CircularProgressIndicator(),
);
Widget _buildList(List<Article> data) {
return CupertinoScrollbar(
child: ListView.separated(
separatorBuilder: (_, index) => Divider(),
itemCount: data.length,
itemBuilder: (_, index) => ListTile(
title: Text(
data[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
data[index].url,
style: TextStyle(fontSize: 10),
),
trailing: Text(data[index].time.split(' ')[0]),
)),
);
}
void _doAdd() {
setState(() {
_page++;
_articles = Api.fetch(_page);
});
}
void _doMinus() {
if (_page <= 0) {
return;
}
setState(() {
_page--;
_articles = Api.fetch(_page);
});
}
Widget _buildError(Object error) => Center(
child: Padding(
padding: const EdgeInsets.all(28.0),
child: Text(
'A Error: ${error.toString()}',
style: TextStyle(color: Colors.red, fontSize: 20),
),
),
);
}
复制代码