【 源码之间 - Flutter 】 FutureBuilder 使用

2023-11-11

一、前言:

主要就是请求网络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】. futureFuture<T> 类型----待执行的异步任务
    2】. builderAsyncWidgetBuilder<T>类型----异步组件构造器
    3】. initialDataT 类型----初始数据

``` 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), ), ), ); }

```

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【 源码之间 - Flutter 】 FutureBuilder 使用 的相关文章

随机推荐

  • sar命令

    sar 使用举例 1 输出CPU使用情况的统计信息 2 显示I O和传送速率的统计信息 3 输出内存页面的统计信息 4 输出每秒创建的进程数的进程统计信息 5 输出网络设备状态的统计信息 6 输出网络设备状态的统计信息 查看网络设备故障 7
  • unity前端通过java后端实现短信验证码登录

    一 搭建java后端 1 新建一个springboot项目 初始导入spring boot starter data redis spring boot starter data web lombok依赖 2 进入阿里巴巴短信运营商购买短信
  • 数据结构与算法--树的查找

    树的查找 当用线性表作为表的组织形式时 可以有三种查找法 其中二分查找效率最高 但由于二分查找要求表中结点按关键字有序 且不能用链表作存储结构 因此 当表的插入或删除操作频繁时 为维护表的有序性 势必要移动表中很多结点 这种由移动结点引起的
  • Jmeter系列-控制器Controllers的介绍(8)

    Controllers 简介 JMeter是一款功能强大的性能测试工具 而控制器是JMeter中非常重要的一个组件 控制器用于控制测试计划的执行流程 可以根据需求来控制线程的启动 停止 循环等操作 Jmeter有两种类型的控制器 Sampl
  • JS中setAttribute的使用

    在web开发中 经常会为某个标签设置属性 那么就可以利用js的setAttribute 方法为标签的属性设置值 下面的内容翻译自W3C DOM Level 1中关于setAttribute方法的说明 具体的方法参数如下 object set
  • 如何在MacOS下安装Python3

    对于Python开发者来讲 安装多个版本Python非常常见 原来我写过如何在Linux下安装Python3 今天我们来看下如何在MacOS下安装Python3 mac系统自带python 不过mac系统自带的python版本都是2 x版本
  • css伪类元素实现小圆点效果

    前言 使用伪类元素 before after 来实现 小圆点效果 效果图 实现方式 1 父级元素 postion relative 定位属性 可为absolute 必须 padding left 10px 因为伪类样式一般是在父级附近 根据
  • 2021-08-26

    代码块 地址引用与值引用 python机制的问题 默认地址引用而非值引用 ans append stk 的话 后面修改stk 会使得ans中的值变化 ans stk 1 2 3 ans append stk 此时 ans 1 2 3 stk
  • Pyecharts数据可视化之折线图(阶梯图、平滑曲线图、面积图)、K线图、常用配置项

    安装pyecharts pip install pyecharts U 本次使用jupyter notebook编写代码 折线图 引入相关包 from pyecharts faker import Faker faker数据构造器 from
  • 从创意到实现!GitMind AI一键生成思维导图,让您的灵感瞬间化作可视化成果!

    GitMind AI 一键生成思维导图 提高生产力效率 GitMind AI介绍 作为一款国产思维导图软件 GitMind已经在市场上获得了广泛的认可和使用 它的不断升级和更新也使得它越来越强大和好用 其中 最新推出的GitMind AI更
  • 《实时渲染》

    实时渲染 第四版
  • 【经典排序算法】3. 插入排序

    对顺序性强的数据 插入排序就比较快 最好情况O n 代码如下 public class Main public static void main String args int arr 3 3 5 6 2 1 arrPrint arr In
  • 后台输出二维码流并展示在前端页面

    后台生成了二维码 现需要将生成的二维码展示到前端页面 为了实现这个功能 在网上找了很久 大多都是建议使用window URL createObjectURL 贴上代码 let oid 934a1fca dc1d 4fe5 888b 23c7
  • 吴恩达深度学习 —— 3.10 直观理解反向传播

    z 1 W
  • 记一次nacos自动更新导致druird属性数据更新异常

    现象 org springframework boot context properties bind BindException Failed to bind properties under spring druid to javax
  • Android利用LocalSocket实现Java端进程与C端进程之间的IPC

    Android是建立在Linux之上的OS 在涉及到安全 网络协议 文件加密等功能时 往往需要通过C语言调用底层API来实现 而如何发出指令让C端执行我们想要的功能 并且在执行之后有返回结果呢 这就需要打通Java端进程和C端进程 使之能高
  • 深圳文件存放服务器,深圳存储服务器

    深圳存储服务器 内容精选 换一换 区块链服务状态为 异常 排查项一 区块链依赖的集群 服务器 存储等资源是否正常 排查项二 云服务器节点资源规格不足 排查项一 区块链依赖的集群 服务器 存储等资源是否正常 CCE集群状态排查 登录CCE控制
  • 生成矩阵的几种方法

    生成矩阵的几种方法 在 MATLAB 中 生成矩阵有许多种方法 下面介绍几种比较常用的方法 使用 zeros 或 ones 函数 可以使用 MATLAB 中的 zeros 或 ones 函数来创建一个特定大小的全零或全一矩阵 这两个函数的语
  • 数据结构归纳总结之概述

    1 谈谈你对数据结构的理解 学习数据结构之前 每每听到数据结构一词 瞬间就有种很高端的感觉 有木有 如果某人嘴边顺口就溜出一串关于数据结构的解说时 心中油然而生出一种仰佩之情 难道这就是传说的专业人士与菜鸟的区别 学习数据结构时 我觉得务必
  • 【 源码之间 - Flutter 】 FutureBuilder 使用

    一 前言 主要就是请求网络api 返回数据 展业界面 根据不同的状态显示不同的界面 加载中 加载完成 加载失败 一 示例demo详述 1 关于异步请求 FutureBuilder需要一个异步任务作为构造入参 通过wanandroid的开发a