一、前言:
主要就是请求网络api,返回数据,展业界面。根据不同的状态显示不同的界面。
加载中 | 加载完成| 加载失败 ---|---|---| | |
一、示例demo详述:
1.关于异步请求
FutureBuilder需要一个异步任务作为构造入参
通过wanandroid的开发api进行文章列表的获取, Api.fetch(int page)
``` class Api { static Future
> 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
{ Future
> _articles; var _page = 0;
@override void initState() { articles = Api.fetch(page); super.initState(); }
@override Widget build(BuildContext context) { return Scaffold( //略... ), body: FutureBuilder
>( 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
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
future; final AsyncWidgetBuilder
builder; final T initialData; ```
2. _FutureBuilderState状态类
- StatefulWidget主要依赖State进行构建组件,所以这里重要的是
_FutureBuilderState
> 其中有两个成员变量_activeCallbackIdentity
和_snapshot
``` class _FutureBuilderState
extends State
> {
Object _activeCallbackIdentity; AsyncSnapshot
_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
> 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
> 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
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
> 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
{ Future
> _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:
[ IconButton( icon: Icon(Icons.remove), onPressed: _doMinus, ) ], ), body: FutureBuilder
>( future: _articles, builder: _builderList, ), ); }
Widget _builderList( BuildContext context, AsyncSnapshot
> 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
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), ), ), ); }
```