源码之间
吧源码之间
是张风捷特烈
在bilibili
的直播间,版权全部。全部视频资源
都将是免费的
,容许被录制
、加工
和随意传播
。禁止使用
源码之间的视频资源作任何盈利行为
的是事,违者必究。源码的分析
,也多是分享和研究
某一编程问题。FutureBuilder源码分析: 录播视屏: www.bilibili.com/video/BV1We…
示例demo的代码贴在文尾,能够本身跑跑,调试看看。android
主要就是请求网络api,返回数据,展业界面。根据不一样的状态显示不一样的界面。git
加载中 | 加载完成 | 加载失败 |
---|---|---|
![]() |
![]() |
![]() |
FutureBuilder须要一个异步任务做为构造入参
经过wanandroid的开发api进行文章列表的获取,Api.fetch(int page)
github
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}';
}
}
复制代码
先定义异步任务和当前页码,在使用
FutureBuilder
进行构造组件。全代码见文尾。json
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,
),
);
}
复制代码
List<Article>
1】.
future
:Future<T> 类型
----待执行的异步任务
2】.builder
:AsyncWidgetBuilder<T>类型
----异步组件构造器
3】.initialData
:T 类型
----初始数据api
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;
复制代码
_FutureBuilderState
其中有两个成员变量
_activeCallbackIdentity
和_snapshot
bash
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();
}
复制代码
因此先看一下
_snapshot
对象所对应的AsyncSnapshot<T>
类
它核心是三个成员变量,记录状态、数据和异常状况
而且提供一些命名构造
方便建立对象和一些get方法
方便使用网络
final ConnectionState connectionState; # 链接状态
final T data; # 数据
final Object error; # 异常
复制代码
简单来讲,就是装着三个东西的瓶子。
app
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();
}
复制代码
_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);
}
}
复制代码
这里是用来外部传的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;
}
}
复制代码
_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的能力就是
根据异步任务的执行状况,向外界暴露出状态方便构建不一样的界面
。
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 源码
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}';
}
}
复制代码
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),
),
),
);
}
复制代码