源碼之間
吧源碼之間
是張風捷特烈
在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),
),
),
);
}
複製代碼