精华内容
下载资源
问答
  • FutureBuilder源码分析

    2019-06-25 17:20:00
    关于FutureBuilder的使用,我在之前的公众号文章中有写过,如果没看过的可以跳转:Flutter FutureBuilder 异步UI神器.FutureBui...
        

    关于 FutureBuilder 的使用,我在之前的公众号文章中有写过,

    如果没看过的可以跳转:Flutter FutureBuilder 异步UI神器.

    FutureBuilder

    首先看 FutureBuilder<T> 类。

    构造函数

    const FutureBuilder({	
      Key key,	
      this.future,	
      this.initialData,	
      @required this.builder,	
    }) : assert(builder != null),	
    super(key: key);

    构造函数很简单,上一篇文章也说过,主要就是三个参数:

    future:是我们的异步请求,该异步请求必须不能在 build 方法中初始化!initialData:如果Future 没有完成的情况下展示该数据builder:构建我们的UI

    AsyncWidgetBuilder

    其中 builder 的类型为 AsyncWidgetBuilder,我们来看一下:

    typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnapshot<T> snapshot);

    其中 typedef 是为函数起别名用的,

    也就是说 builder 是一个方法,从而在定义builder的时候就要实现这个方法。

    AsyncSnapshot

    接着看一下 snapshot

    @immutable	
    class AsyncSnapshot<T> {	
      /// Creates an [AsyncSnapshot] with the specified [connectionState],	
      /// and optionally either [data] or [error] (but not both).	
      const AsyncSnapshot._(this.connectionState, this.data, this.error)	
        : assert(connectionState != null),	
          assert(!(data != null && error != null));	
    	
      /// Creates an [AsyncSnapshot] in [ConnectionState.none] with null data and error.	
      const AsyncSnapshot.nothing() : this._(ConnectionState.none, null, null);	
    	
      /// Creates an [AsyncSnapshot] in the specified [state] and with the specified [data].	
      const AsyncSnapshot.withData(ConnectionState state, T data) : this._(state, data, null);	
    	
      /// Creates an [AsyncSnapshot] in the specified [state] and with the specified [error].	
      const AsyncSnapshot.withError(ConnectionState state, Object error) : this._(state, null, error);	
    	
      /// Current state of connection to the asynchronous computation.	
      final ConnectionState connectionState;	
    	
      /// The latest data received by the asynchronous computation.	
      ///	
      /// If this is non-null, [hasData] will be true.	
      ///	
      /// If [error] is not null, this will be null. See [hasError].	
      ///	
      /// If the asynchronous computation has never returned a value, this may be	
      /// set to an initial data value specified by the relevant widget. See	
      /// [FutureBuilder.initialData] and [StreamBuilder.initialData].	
      final T data;	
    	
      /// Returns latest data received, failing if there is no data.	
      ///	
      /// Throws [error], if [hasError]. Throws [StateError], if neither [hasData]	
      /// nor [hasError].	
      T get requireData {	
        if (hasData)	
          return data;	
        if (hasError)	
          throw error;	
        throw StateError('Snapshot has neither data nor error');	
      }	
    	
      /// The latest error object received by the asynchronous computation.	
      ///	
      /// If this is non-null, [hasError] will be true.	
      ///	
      /// If [data] is not null, this will be null.	
      final Object error;	
    	
      /// Returns a snapshot like this one, but in the specified [state].	
      ///	
      /// The [data] and [error] fields persist unmodified, even if the new state is	
      /// [ConnectionState.none].	
      AsyncSnapshot<T> inState(ConnectionState state) => AsyncSnapshot<T>._(state, data, error);	
    	
      /// Returns whether this snapshot contains a non-null [data] value.	
      ///	
      /// This can be false even when the asynchronous computation has completed	
      /// successfully, if the computation did not return a non-null value. For	
      /// example, a [Future<void>] will complete with the null value even if it	
      /// completes successfully.	
      bool get hasData => data != null;	
    	
      /// Returns whether this snapshot contains a non-null [error] value.	
      ///	
      /// This is always true if the asynchronous computation's last result was	
      /// failure.	
      bool get hasError => error != null;	
    	
    }

    前面定义了一个私有的构造函数 const AsyncSnapshot._(this.connectionState, this.data, this.error)

    后面用命名构造函数来调用私有构造函数返回一个 snapshot。

    也可以看到 hasData hasError 其实就是判断 data/error 是否等于 null。

    _FutureBuilderState

    重点是 _FutureBuilderState<T>,还是从上往下看,

    首先定义了两个私有变量:

    /// An object that identifies the currently active callbacks. Used to avoid	
    /// calling setState from stale callbacks, e.g. after disposal of this state,	
    /// or after widget reconfiguration to a new Future.	
    Object _activeCallbackIdentity;	
    AsyncSnapshot<T> _snapshot;

    _activeCallbackIdentity 根据注释来解释大概就是:标记当前还存活的对象,用于避免已经dispose了还调用setState。

    _snapshot 就是我们刚才说用来返回数据的。

    initState()

    接着是初始化方法:

    @override	
    void initState() {	
      super.initState();	
      _snapshot = AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData);	
      _subscribe();	
    }

    首先根据传入的 initialData初始化_snapshot,

    然后调用_subscribe()

    _subscribe()

    看一下 _subscribe() 方法 :

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

    这里做了如下几件事:

    1.  判断 future 是否为null;

       2.如果不为null,则初始化 _activeCallbackIdentity 为 Object();   3.变更 _snapshot 的状态为 ConnectionState.waiting;   4.接着对 Future 调用 then 方法,这里主要就是先判断了 callbackIdentity是否相等,如果不相等,那么这个 Future肯定是更改了,或者已经 dispose 了。如果 callbackIdentity 相等,则继续判断是有错误还是有数据,有数据就调用 AsyncSnapshot<T>.withData,有错误就调用 AsyncSnapshot<T>.withError,并传入状态。


    didUpdateWidget

    接着下面是 didUpdateWidget 方法,该方法主要是用来判断是否需要更新 widget:

    @override	
    void didUpdateWidget(FutureBuilder<T> oldWidget) {	
      super.didUpdateWidget(oldWidget);	
      if (oldWidget.future != widget.future) {	
        if (_activeCallbackIdentity != null) {	
          _unsubscribe();	
          _snapshot = _snapshot.inState(ConnectionState.none);	
        }	
        _subscribe();	
      }	
    }

    这里更新的逻辑是判断 future 是否一样,如果不一样则:

    1.判断 _activeCallbackIdentity 是否为 null2.unsubscribe(),取消订阅。这里就一行代码:_activeCallbackIdentity = null;3.把 _snapshot 的状态置为 ConnectionState.none4.subscribe(),重新订阅。

    dispose()

    最后就是 dispose()方法:

    @override	
    void dispose() {	
      _unsubscribe();	
      super.dispose();	
    }

    FutureBuilder 重写该方法来达到 dispose 时自动取消订阅。

    总结

    Future 的状态无非三种:

    1.未开始2.进行中3.已完成

    其中 已完成 又分为两种:

    1.有数据2.有异常

    其实可以看到,FutureBuilder 大体上的思路就是对 Future 状态的封装,从而达到我们想要的效果。

    在 Flutter 中,我们可以通过查看源码来获取很多的灵感,因为 Flutter 的 注释写的简直不要太到位

    640?wx_fmt=jpeg


    展开全文
  • 主要给大家介绍了关于如何利用FutureBuilder提高开发效率的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
  • 如何使用 Flutter FutureBuilder 优雅构建异步UI, StreamBuilder 和 FutureBuilder 之间的异同点

    Flutter系列文章目录导读:

    (一)Flutter学习之Dart变量和类型系统
    (二)Flutter学习之Dart展开操作符 和 Control Flow Collections
    (三)Flutter学习之Dart函数
    (四)Flutter学习之Dart操作符、控制流和异常处理
    (五)Flutter学习之Dart面向对象
    (六)Flutter学习之Dart异步操作详解
    (七)Flutter 学习之开发环境搭建
    (八)Flutter 和 Native 之间的通信详解
    (九)Android 项目集成 Flutter 模块
    (十)Flutter FutureBuilder 优雅构建异步UI
    更新中…


    前言

    在实际开发中, 一般在展示列表内容之前需要先展示一个 loading 表示正在加载, 当加载成功后展示列表内容, 加载失败展示失败的界面

    所以, 这样一个需求就涉及到了三种情况:

    • 加载中
    • 加载成功展示列表
    • 加载失败展示错误

    从前面的文章(《(二)Flutter 学习之 Dart 展开操作符和 Control Flow Collections》)[https://chiclaim.blog.csdn.net/article/details/94617048] 我们知道 Flutter UI声明式UI

    不同UI的切换时通过 setState 来重新构建的. 那么上面的三种情况UI 我们需要通过 if else 来判断到底展示那种界面.

    例如下面的伪代码:

    @override
    Widget build(BuildContext context) {
        if(loading) { // 正在加载
          return Text("Loading...");
        } else if(isError) { // 加载出错
          return Text("Error...");
        } else {   // 展示列表内容
          return ListView(...)
        }
    }
    

    这种方式虽然也能实现上面的需求, 但是不利于代码的维护, 需要维护很多变量, 很不优雅.

    FutureBuilder

    FutureBuilder 的用法很简单, 主要涉及两个参数:

    • future 指定异步任务, 交给 FutureBuilder 管理
    • builder 根据异步任务的状态来构建不同的 Widget, 类似上面的 if/else

    FutureBuilder 中的异步任务状态有:

    状态 描述
    none 没有连接到任何异步任务
    waiting 已连接到异步任务等待被交互
    active 已连接到一个已激活的异步任务
    done 已连接到一个已结束的异步任务

    我们可以使用 FutureBuilder 改造上面的案例, 代码如下所示:

    FutureBuilder<int>(
        future: _loadList(),
        builder: (context, snapshot) {
          switch (snapshot.connectionState) {
            case ConnectionState.none:
            case ConnectionState.waiting:
            case ConnectionState.active:
              // 显示正在加载
              return createLoadingWidget();
            case ConnectionState.done:
              // 提示错误信息
              if (snapshot.hasError) {
                return createErrorWidget(snapshot.error.toString());
              }
              // 展示列表内容
              return ListView.separated(
                itemCount: snapshot.data,
                itemBuilder: (BuildContext context, int index) {
                  return ListTile(title: Text(index.toString()));
                },
                separatorBuilder: (BuildContext context, int index) {
                  return divider;
                },
              );
            default:
              return Text("unknown state");
    )
    

    需要注意的是, 上面的代码界面每次被重建的时候都会执行 loadList 操作.

    但是有的时候并不是界面发生变化的时候都需要去重新执行 future, 例如界面一个 Tab + ListView(文章分类+文章列表), 文章分类是需要先加载, 那么文章分类的异步任务就是 future, 加载成功分类后, 才能去加载文章列表, 列表加载成功界面会重新构建, 这个时候是不应该再次加载文章分类的(future)

    这个时候需要在把 future 变量作为成员变量, 在 initState 中初始化, 然后再传递给 future 参数, 如:

    Future _future;
    
    @override
    void initState() {
        _future = _loadList();
        super.initState();
    }
    
    FutureBuilder<int>(
        future: _future,
        ...
    )
    

    运行效果如下图所示:

    Flutter-BuilderFuture

    FutureBuilder 源码分析

    FutureBuilder 继承了 StatefulWidget, 所以主要代码都集中在 State

    class _FutureBuilderState<T> extends State<FutureBuilder<T>> {
      Object _activeCallbackIdentity;
      AsyncSnapshot<T> _snapshot;
    
      @override
      void initState() {
        super.initState();
        // 初始化异步快照, 初始状态为 none
        _snapshot = AsyncSnapshot<T>.withData(ConnectionState.none, widget.initialData);
        // 关联异步任务
        _subscribe();
      }
    
      // 页面发生变化判断老的widget的 future 和新widget future 是否是同一个对象
      // 如果是同一个对象则不会执行异步任务, 否则会重新执行异步任务
      @override
      void didUpdateWidget(FutureBuilder<T> oldWidget) {
        super.didUpdateWidget(oldWidget);
        if (oldWidget.future != widget.future) {
          if (_activeCallbackIdentity != null) {
            _unsubscribe();
            _snapshot = _snapshot.inState(ConnectionState.none);
          }
          _subscribe();
        }
      }
    
      // 执行外部传入的 builder 回调
      // widget 就是 State 对应的 FutureBuilder(StatefulWidget)
      @override
      Widget build(BuildContext context) => widget.builder(context, _snapshot);
    
      @override
      void dispose() {
        _unsubscribe();
        super.dispose();
      }
    
      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);
              });
            }
          });
          
          // 将异步任务状态设置为 waiting
          _snapshot = _snapshot.inState(ConnectionState.waiting);
        }
      }
    
      void _unsubscribe() {
        _activeCallbackIdentity = null;
      }
    

    StreamBuilder

    除了 FutureBuilder 可以优雅构建异步UI, StreamBuilder 也可以实现, 但是一般的异步任务 UI 展示并不是一个 Stream 流的形式, 更像是一次性的逻辑处理, 只要成功后, 一般不需要更新, 所以使用 FutureBuilder 就完全够了. 实际开发中根据情况来选择. StreamBuilder 的功能更加强大, 后期如果往 stream 中发送数据 UI 界面也跟着发生变化 如:

    StreamBuilder<int>(
      // 这个是stream 而不是 future
      stream: _streamController.stream,
      initialData: _counter,
      builder: (BuildContext context, AsyncSnapshot<int> snapshot){
        // 接收到 controller 发送给 stream 的数据
        return Text('${snapshot.data}');
      }
    ),
    )
    

    我们可以通过_streamController 发送数据, 然后会自动调用 StreamBuilder builder 回调, 从而刷新 Widget

    _streamController.sink.add(++_counter);
    

    当然也可以不通过 StreamController 来提供 stream, 也可以创建一个函数返回 stream, 具体如何创建可以查看我之前的文章 《(六)Flutter 学习之 Dart 异步操作详解》


    如果你觉得本文帮助到你,给我个关注和赞呗!

    与此同时,我编写了一份: 超详细的 Android 程序员所需要的技术栈思维导图

    如果有需要可以移步到我的 GitHub -> AndroidAll,里面包含了最全的目录和对应知识点链接,帮你扫除 Android 知识点盲区。 由于篇幅原因只展示了 Android 思维导图:超详细的Android技术栈

    展开全文
  • 注意:无特殊说明,Flutter版本及...当有一个Future(异步)任务需要展示给用户时,可以使用FutureBuilder控件来完成,比如向服务器发送数据成功时显示成功提示: var _future = Future.delayed(Duration(seconds:...

    注意:无特殊说明,Flutter版本及Dart版本如下:

    • Flutter版本: 1.12.13+hotfix.5
    • Dart版本: 2.7.0

    展示异步任务状态

    当有一个Future(异步)任务需要展示给用户时,可以使用FutureBuilder控件来完成,比如向服务器发送数据成功时显示成功提示:

    var _future = Future.delayed(Duration(seconds: 3), () {
        return '老孟,一个有态度的程序员';
      });
    
    FutureBuilder(
          future: _future,
          builder: (context, snapshot) {
            var widget;
            if (snapshot.connectionState == ConnectionState.done) {
              if (snapshot.hasError) {
                widget = Icon(
                  Icons.error,
                  color: Colors.red,
                  size: 48,
                );
              } else {
                widget = Icon(
                  Icons.check_circle,
                  color: Colors.green,
                  size: 36,
                );
              }
            } else {
              widget = Padding(
                padding: EdgeInsets.all(20),
                child: CircularProgressIndicator(),
              );
            }
    
            return Center(
              child: Container(
                height: 100,
                width: 100,
                decoration: BoxDecoration(
                    border: Border.all(color: Colors.grey),
                    borderRadius: BorderRadius.all(Radius.circular(10))),
                child: widget,
              ),
            );
          },
        );
    

    效果如下:

    在Future任务中出现异常如何处理,下面模拟出现异常,修改_future:

    var _future = Future.delayed(Duration(seconds: 3), () {
        return Future.error('');
      });
    

    效果如下:

    builder是FutureBuilder的构建函数,在这里可以判断状态及数据显示不同的UI,
    ConnectionState的状态包含四种:nonewaitingactivedone,但我们只需要关注done状态,此状态表示Future执行完成,snapshot参数的类型是AsyncSnapshot<T>

    ListView加载网络数据

    FutureBuilder还有一个比较常用的场景:网络加载数据并列表展示,这是一个非常常见的功能,在网络请求过程中显示loading,请求失败时显示失败UI,成功时显示成功UI。

    模拟成功网络请求,通常会返回json字符串:

    var _future = Future.delayed(Duration(seconds: 3), () {
        return 'json 字符串';
      });
    

    构建FutureBuilder控件:

    FutureBuilder(
          future: _future,
          builder: (context, snapshot) {
            var widget;
            if (snapshot.connectionState == ConnectionState.done) {
              if (snapshot.hasError) {
                widget = _loadingErrorWidget();
              } else {
                widget = _dataWidget(snapshot.data);
              }
            } else {
              widget = _loadingWidget();
            }
            return widget;
          },
        );
    

    构建loading控件:

    _loadingWidget() {
        return Center(
          child: Padding(
            padding: EdgeInsets.all(20),
            child: CircularProgressIndicator(),
          ),
        );
      }
    

    构建网络加载失败控件:

    _loadingErrorWidget() {
        return Center(
          child: Text('数据加载失败,请重试。'),
        );
      }
    

    数据加载成功,构建数据展示控件:

    _dataWidget(data) {
        return ListView.separated(
          itemBuilder: (context, index) {
            return Container(
              height: 60,
              alignment: Alignment.center,
              child: Text(
                '$index',
                style: TextStyle(fontSize: 20),
              ),
            );
          },
          separatorBuilder: (context, index) {
            return Divider();
          },
          itemCount: 10,
        );
      }
    

    效果如下:

    模拟网络加载失败:

    var _future = Future.delayed(Duration(seconds: 3), () {
        return Future.error('');
      });
    

    效果如下:

    通过上面的示例说明FutureBuilder控件极大的简化了异步任务相关显示的控件,不再需要开发者自己维护各种状态以及更新时调用State.setState

    防止FutureBuilder重绘

    FutureBuilder是一个StatefulWidget控件,如果在FutureBuilder控件节点的父节点重绘rebuild,那么FutureBuilder也会重绘,这不仅耗费不必要的资源,如果是网络请求还会消耗用户的流量,这是非常糟糕的体验,如何解决这个问题?

    通过源代码发现FutureBuilder重绘逻辑是这样的:

    @override
      void didUpdateWidget(FutureBuilder<T> oldWidget) {
        super.didUpdateWidget(oldWidget);
        if (oldWidget.future != widget.future) {
          if (_activeCallbackIdentity != null) {
            _unsubscribe();
            _snapshot = _snapshot.inState(ConnectionState.none);
          }
          _subscribe();
        }
      }
    

    FutureBuilder在重建时判断旧的future和新的future是否相等,如果不相等才会重建,所以我们只需要让其相等即可,有人可能会以为设置的future是同一个函数,如下:

     _future() async{
        ...
      }
    
    FutureBuilder(
    	future: _future(),
    	...
    )
    

    上面的方式是不相等的,是错误的用法,可以将_future方法赋值给变量:

    var _mFuture;
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        _mFuture = _future();
      }
    
     _future() async{
        ...
      }
    
    FutureBuilder(
    	future: _mFuture,
    	...
    )
    

    这才是正确的用法。

    交流

    如果你对Flutter还有疑问或者技术方面的疑惑,欢迎加入Flutter交流群(微信:laomengit)。

    同时也欢迎关注我的Flutter公众号【老孟程序员】,公众号首发Flutter的相关内容。

    Flutter地址:http://laomengit.com 里面包含160多个组件的详细用法。

    展开全文
  • 即使AppBar和FutureBuilder没有任何关联,每次我们改变它的值(通过调用setState), FutureBuilder都会再次经历整个生命周期!它重新取代future,导致不必要的流量,并再次显示负载,导致糟糕的用户体验。 这个...

    前言:

    我们经常有这样的一个开发场景:一个页面进入之后先进行网络请求,此时显示一个圆圈(等待动画),等网络数据返回时显示一个展示网络数据的布局。例如下图:
    在这里插入图片描述

    我们通常的做法是

    if(data==null){
    	return CircularProgressIndicator();
    }else{
    	return ListView(...);
    }
    

    大致就是数据返回之前我们加载一个组件,等数据返回值后,我们重绘页面返回另一个组件。
    在flutter中,有一个新的实现方式,那就是我们即将要介绍的futureBuilder.

    FutureBuilder用法和实现

    Widget that builds itself based on the latest snapshot of interaction with a Future.

    官方意思是一个基于与Future交互的最新快照构建自己的小部件。

    先看一下它的构造方法:

      const FutureBuilder({
        Key key,
        this.future,          //获取数据的方法
        this.initialData,   //初始的默认数据
        @required this.builder
      }) : assert(builder != null),
           super(key: key);
    

    主要看一下builder,这个是我们主要关心的,它是我们构建组件的策略。
    接收两个参数:BuildContext context, AsyncSnapshot snapshot.
    context就不解释了,snapshot就是_calculation在时间轴上执行过程的状态快照。

    //FutureBuilder控件
    new FutureBuilder<String>(
      future: _calculation, // 用户定义的需要异步执行的代码,类型为Future<String>或者null的变量或函数
      builder: (BuildContext context, AsyncSnapshot<String> snapshot) {      //snapshot就是_calculation在时间轴上执行过程的状态快照
        switch (snapshot.connectionState) {
          case ConnectionState.none: return new Text('Press button to start');    //如果_calculation未执行则提示:请点击开始
          case ConnectionState.waiting: return new Text('Awaiting result...');  //如果_calculation正在执行则提示:加载中
          default:    //如果_calculation执行完毕
            if (snapshot.hasError)    //若_calculation执行出现异常
              return new Text('Error: ${snapshot.error}');
            else    //若_calculation执行正常完成
              return new Text('Result: ${snapshot.data}');
        }
      },
    )
    

    FutureBuilder通过子属性future获取用户需要异步处理的代码,用builder回调函数暴露出异步执行过程中的快照。我们通过builder的参数snapshot暴露的快照属性,定义好对应状态下的处理代码,即可实现异步执行时的交互逻辑。

    看起来似乎有点绕口,我们看看下面这段代码:

    /*
     * Created by 李卓原 on 2018/9/30.
     * email: zhuoyuan93@gmail.com
     * 关于状态改变引起的不必要的页面刷新:https://github.com/flutter/flutter/issues/11426#issuecomment-414047398
     */
    
    import 'dart:async';
    
    import 'package:async/async.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter_app/utils/HttpUtil.dart';
    
    class FutureBuilderPage extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => FutureBuilderState();
    }
    
    class FutureBuilderState extends State<FutureBuilderPage> {
      String title = 'FutureBuilder使用';
      
      Future _gerData() async {
        var response = HttpUtil()
            .get('http://api.douban.com/v2/movie/top250', data: {'count': 15});
        return response;
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(title),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              setState(() {
                title = title + '.';
              });
            },
            child: Icon(Icons.title),
          ),
        body: FutureBuilder(
            builder: _buildFuture,
            future: _gerData(), // 用户定义的需要异步执行的代码,类型为Future<String>或者null的变量或函数
          ),
        );
      }
    
      ///snapshot就是_calculation在时间轴上执行过程的状态快照
      Widget _buildFuture(BuildContext context, AsyncSnapshot snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.none:
            print('还没有开始网络请求');
            return Text('还没有开始网络请求');
          case ConnectionState.active:
            print('active');
            return Text('ConnectionState.active');
          case ConnectionState.waiting:
            print('waiting');
            return Center(
              child: CircularProgressIndicator(),
            );
          case ConnectionState.done:
            print('done');
            if (snapshot.hasError) return Text('Error: ${snapshot.error}');
            return _createListView(context, snapshot);
          default:
            return null;
        }
      }
    
      Widget _createListView(BuildContext context, AsyncSnapshot snapshot) {
        List movies = snapshot.data['subjects'];
        return ListView.builder(
          itemBuilder: (context, index) => _itemBuilder(context, index, movies),
          itemCount: movies.length * 2,
        );
      }
    
      Widget _itemBuilder(BuildContext context, int index, movies) {
        if (index.isOdd) {
          return Divider();
        }
        index = index ~/ 2;
        return ListTile(
          title: Text(movies[index]['title']),
          leading: Text(movies[index]['year']),
          trailing: Text(movies[index]['original_title']),
        );
      }
    }
    

    在build方法中,我们返回了一个Scaffold,主要的代码在body中,包裹了一个FutureBuilder,
    我们在它的builder方法中,对不同状态返回了不同的控件。

    snapshot.connectionState就是异步函数_gerData的执行状态,用户通过定义在ConnectionState.noneConnectionState.waiting状态下,输出一个Text和居中·(Center)·显示并且内置文字CircularProgressIndicator的组件,其意义即:当异步函数_gerData未执行时,屏幕正中央显示文字:还没有开始网络请求。和正在执行时,显示一个刷新状态的控件。

    _gerData执行完毕后,snapshot.connectionState的值即变为ConnectionState.done,此时即可输出根据HTTP请求获取到的数据生成对应的ListItem。由于ConnectionState.done是除了ConnectionState.noneConnectionState.waiting以外的唯一值,所以代码中在switch下用default也可(ConnectionState.active好像在整个过程中没有调用)。

    由于通过FutureBuilder内的builder()函数即可操控控件的状态和重绘,我们不必通过自己写异步状态的判断和多次使用setState()实现页面上加载中和加载完成显示效果的切换,因为FutureBuilder内部自带了执行setState()的方法。

    现在一个FutureBuilder的构建就算完成了。

    防止FutureBuilder进行不必要的重绘

    如果只是写一个FutureBuilder,我们就不需要floatingActionButton里的一系列东西,所以这时候就到它的出场了。
    代码中的意思,每次点击它,就在我们标题后面加一个“.” , 看一下效果

    在这里插入图片描述
    确实是改变了标题,但是整个页面也随着setState而进行了不必要的重绘,这就是我们本篇的重点了。

    即使AppBar和FutureBuilder没有任何关联,每次我们改变它的值(通过调用setState), FutureBuilder都会再次经历整个生命周期!它重新取代future,导致不必要的流量,并再次显示负载,导致糟糕的用户体验。

    这个问题以各种方式表现出来。在某些情况下,它甚至不像上面的例子那么明显。例如:

    • 从当前不在屏幕上的页面生成的网络流量
    • 热重装不能正常工作
    • 更新某些“继承的窗口小部件”中的值时丢失导航器状态
    • 等等…

    但是这一切的原因是什么?我们如何解决它?

    didUpdateWidget问题

    注意:在本节中,我将详细介绍FutureBuilder的工作原理。如果您对此不感兴趣,可以跳到解决方案。

    如果我们仔细看看代码FutureBuilder,我们发现它是一个StatefulWidget。我们知道,StatefulWidgets维护一个长期存在的State对象。这种状态有一些管理其生命周期的方法,就像方法initStatebuilddidUpdateWidget

    initState在第一次创建状态对象时只调用一次,并且build每次我们需要构建要显示的窗口小部件时调用它,但是那是什么didUpdateWidget呢?只要附加到此State对象的窗口小部件发生更改,就会调用此方法。
    当使用新输入重建窗口小部件时,将放置旧窗口小部件,并创建新窗口小部件并将其分配给State对象,并didUpdateWidget在重建之前调用它以执行我们想要执行的任何操作。

    FutureBuilder这种情况下,这个方法看起来像这样:

    @override
    void didUpdateWidget(FutureBuilder<T> oldWidget) {
      super.didUpdateWidget(oldWidget);
      if (oldWidget.future != widget.future) {
        if (_activeCallbackIdentity != null) {
          _unsubscribe();
          _snapshot = _snapshot.inState(ConnectionState.none);
        }
        _subscribe();
      }
    }
    

    它基本上是说:如果在重建时,新窗口小部件具有与旧窗口小部件不同的Future实例,则重复所有内容:取消订阅,并再次订阅。

    但我们不是提供相同的Future吗?我们称之为同一个功能!好吧,Future的情况不一样了。我们的功能正在完成同样的工作,但随后又回归了一个与旧的不同的新Future。

    因此,我们想要做的是在第一次调用时存储或缓存函数的输出,然后在再次调用函数时提供相同的输出。此过程称为记忆(memoization)。

    解决方案 1 :Memoize the future

    简单来说,Memoization缓存函数的返回值,并在再次调用该函数时重用它。Memoization主要用于函数式语言,其中函数是确定性的(它们总是为相同的输入返回相同的输出),但我们可以在这里使用简单的memoization来解决我们的问题,以确保FutureBuilder始终接收相同的未来实例。

    为此,我们将使用DartAsyncMemoizer。这个记忆器完全符合我们的要求!它需要一个异步函数,在第一次调用它时调用它,并缓存其结果。对于该函数的所有后续调用,memoizer返回相同的先前计算的未来。

    因此,为了解决我们的问题,我们首先在我们的小部件中创建一个AsyncMemoizer实例:

    final AsyncMemoizer _memoizer = AsyncMemoizer();

    注意:你不应该在StatelessWidget中实例化memoizer,因为Flutter在每次重建时都会处理StatelessWidgets,这基本上可以达到目的。您应该在StatefulWidget中实例化它,或者在它可以持久化的地方实例化它。

    之后,我们将修改_fetchData函数以使用该memoizer:

    _gerData() {
        return _memoizer.runOnce(() async {
          return await HttpUtil()
              .get('http://api.douban.com/v2/movie/top250', data: {'count': 15});
        });
     }
     
    

    我们用AsyncMemoizer.runOnce包装我们的函数,它完全听起来像它的声音;它只运行一次该函数,并在再次调用时返回缓存的Future。
    就是这样!我们的FutureBuilder现在只是第一次触发:
    在这里插入图片描述

    现在,我们其他地方进行setState也不会导致FutureBuilder的重绘了。
    为了解决这个问题,我们使用Dart的AsyncMemoizer每次都传递相同的Future实例。

    解决方法2 在构建函数之外调用Future

    问题是每次发布重建时都会调用FutureBuilder状态的didUpdateWidget。此函数检查旧的future对象是否与新的对象不同,如果是,则重新启动FutureBuilder。为了解决这个问题,我们可以在构建函数之外的某个地方调用Future。例如,在initState中,将其保存在成员变量中,并将此变量传递给FutureBuilder

    比如:

    var _futureBuilderFuture;
    ...
    
    @override
    void initState() { 
        ///用_futureBuilderFuture来保存_gerData()的结果,以避免不必要的ui重绘
        _futureBuilderFuture = _gerData();
      }
    ...
    
    FutureBuilder(
      future: _futureBuilderFuture ,
      ....
    

    这里使用_futureBuilderFuture来保存_gerData()的结果,这样我们传递给FutureBuilder的是一个成员变量,而不是一个方法就不会多次调用了。
    看一下完整代码:

    /*
    * Created by 李卓原 on 2018/9/30.
    * email: zhuoyuan93@gmail.com
    * 关于状态改变引起的不必要的页面刷新:https://github.com/flutter/flutter/issues/11426#issuecomment-414047398
    */
    
    import 'dart:async';
    
    import 'package:flutter/material.dart';
    import 'package:flutter_app/utils/HttpUtil.dart';
    
    class FutureBuilderPage extends StatefulWidget {
     @override
     State<StatefulWidget> createState() => FutureBuilderState();
    }
    
    class FutureBuilderState extends State<FutureBuilderPage> {
     String title = 'FutureBuilder使用';
     var _futureBuilderFuture;
    
     Future _gerData() async {
       var response = HttpUtil()
           .get('http://api.douban.com/v2/movie/top250', data: {'count': 15});
       return response;
     }
    
     @override
     void initState() {
       // TODO: implement initState
       super.initState();
    
       ///用_futureBuilderFuture来保存_gerData()的结果,以避免不必要的ui重绘
       _futureBuilderFuture = _gerData();
     }
    
     @override
     Widget build(BuildContext context) {
       return Scaffold(
         appBar: AppBar(
           title: Text(title),
         ),
         floatingActionButton: FloatingActionButton(
           onPressed: () {
             setState(() {
               title = title + '.';
             });
           },
           child: Icon(Icons.title),
         ),
         body: RefreshIndicator(
           onRefresh: _gerData,
           child: FutureBuilder(
             builder: _buildFuture,
             future:
                 _futureBuilderFuture, // 用户定义的需要异步执行的代码,类型为Future<String>或者null的变量或函数
           ),
         ),
       );
     }
    
     ///snapshot就是_calculation在时间轴上执行过程的状态快照
     Widget _buildFuture(BuildContext context, AsyncSnapshot snapshot) {
       switch (snapshot.connectionState) {
         case ConnectionState.none:
           print('还没有开始网络请求');
           return Text('还没有开始网络请求');
         case ConnectionState.active:
           print('active');
           return Text('ConnectionState.active');
         case ConnectionState.waiting:
           print('waiting');
           return Center(
             child: CircularProgressIndicator(),
           );
         case ConnectionState.done:
           print('done');
           if (snapshot.hasError) return Text('Error: ${snapshot.error}');
           return _createListView(context, snapshot);
         default:
           return Text('还没有开始网络请求');
       }
     }
    
     Widget _createListView(BuildContext context, AsyncSnapshot snapshot) {
       List movies = snapshot.data['subjects'];
       return ListView.builder(
         itemBuilder: (context, index) => _itemBuilder(context, index, movies),
         itemCount: movies.length * 2,
       );
     }
    
     Widget _itemBuilder(BuildContext context, int index, movies) {
       if (index.isOdd) {
         return Divider();
       }
       index = index ~/ 2;
       return ListTile(
         title: Text(movies[index]['title']),
         leading: Text(movies[index]['year']),
         trailing: Text(movies[index]['original_title']),
       );
     }
    }
    
    

    本文代码地址
    网络请求类HttpUtil地址

    展开全文
  • 首先先来介绍一下FutureBuilder的使用: FutureBuilder类主要需要两个参数: future:需要一个异步方法,没有参数传入,如果方法内需要参数可以设置为类参数,或全局参数。将该类方法放在Widget build之上就行。 ...
  • 通过FutureBuilder组件可实现在Flutter中将异步加载的数据更新显示到对应的组件上
  • 一般程序员都会了解,类似于 IO、网络请求等都应该是异步的。 在Dart中,我们使用 Future 来管理,这样就不用担心线程或者死锁的问题。...为此,Flutter 推出 FutureBuilder。 什么是FutureBuilder 先...
  • FutureBuilder FutureBuilder会依赖一个Future,它会根据所依赖的Future的状态来动态构建自身。 future: FutureBuilder依赖的Future,通常是一个异步耗时任务。 initialData: 初始数据,用户设置默认数据。 ...
  • Flutter通过FutureBuilder实现异步请求网络数据并显示加载中Demo,详情请看博客Flutter中的异步(Future、async、await、FutureBuilder)和 网络请求:https://yuzhiqiang.blog.csdn.net/article/details/89155870
  • 经过本人测试,FutureBuilder一般需要抽象出来,有一定的条件才能生效,如下图红框所示。
  • 使用JsonData和Futurebuilder的Listview :smiling_face_with_sunglasses: 你也可以给我买咖啡 :laptop: 要求 :alien: 任何操作系统(MacOS,Linux,Windows) 任何安装了Flutter SDK的IDE(Android Studio,...
  • futurebuilder放进GestureDetector的onTap里FutureBuilder的builder函数参数就不执行了,放在其他地方就正常执行,why? ![图片说明](https://img-ask.csdn.net/upload/201910/24/1571906339_343426.png)
  • Flutter FutureBuilder+Dio异步调用心得

    千次阅读 2020-03-28 23:26:06
    FutureBuilder 要想实现Dio网络请求完成后再加载,必须DIO要返回Response对象才行,不然FutureBuilder拿不到异常,失败了他也不知道,DIO就简单封装一下把URL地址和请求的参数封一下就可以了,返回必须是Response,不要...
  • Flutter的FutureBuilder列表示例 import 'package:flutter/material.dart'; import '../service/service_method.dart'; class CartPage extends StatelessWidget { @override Widget build(BuildContex.....
  • flutter简单的动态组件FutureBuilder

    千次阅读 2019-08-13 22:30:15
    home: Scaffold( body: new FutureBuilder<List<Poster>>( future: findGroupList(), builder: (context, snapshot) { //或者这一行这样也可以,开始好像这样写报错了 if(snaps...
  • FutureBuilder( future: _requestData(),//调用接口方法 生成此方法 红色字体为调用多个接口的操作 Future _requestData() async{ return Future.wait([queryAll('0'),recordUsedCar()]); } 注意:如果这样写...
  • FutureBuilder FutureBuilder的用法很简单, 主要涉及两个参数: 1、future 指定异步任务,交给FutureBuilder 来管理; 2、builder 根据异步任务的状态来构建不同的组件 FutureBuilder异步任务的状态 状态 ...
  • Flutter FutureBuilder FutureBuilder 实际上就是对Future进行封装的一个Widget。我们先来看看他的构造方法 const FutureBuilder({ Key key, this.future, this.initialData, @required this.builder }) 其中, ...
  • 今天给大家介绍一下Future和FutureBuilder异步请求数据,以及FutureBuilder如何进行不必要的重绘~FutureFuture.then(Future的值,{Future异常返回})Future.whenCompleteFuture.timeout结合async,...Future ...
  • Flutter之FutureBuilder的学习和使用

    千次阅读 2018-11-25 13:24:03
    主要是学习FutureBuilder的使用,利用FutureBuilder来实现懒加载,并可以监听加载过程的状态 这个Demo是加载玩Android的一个页面的列表数据 1.需求场景 经常有这些场景,就是先请求网络数据并显示加载菊花,拿到...
  • /// Flutter code sample for FutureBuilder // This sample shows a [FutureBuilder] that displays a loading spinner while it // loads data. It displays a success icon and text if the [Future] completes ...
  • Flutter FutureBuilder 示例

    2021-07-10 16:52:29
    通过示例,可以重点对FutureBuilder的各个属性的了解
  • FlutterBuilder学习 使用异步操作的时候,一定要记住三个状态:等待、错误、正常。 @override Widget build(BuildContext context)... body: FutureBuilder( //TODO: 通过传入的future来监听,future发生变化时,
  • 一、前言: 1.先简单说下源码之间吧 1 】: 源码之间是张风捷特烈在bilibili的直播间,版权所有。 2 】: 源码之间直播和产出的所有视频资源都将是免费的,允许被录制、加工和...FutureBuilder源码分析: 录播视屏: ...
  • 很多时候我们会依赖一些异步数据来动态更新UI,...但由于在实际开发中依赖异步数据更新UI的这种场景非常常见,因此Flutter专门提供了FutureBuilder和StreamBuilder两个组件来快速实现这种功能。 FutureBuilder FutureB
  • 反复调用接口说明调用接口的方法反复被调用了,所以为了页面稳定,需要找到一种值调用一次改接口的方法 添加全局变量 var _futureBuilderFuture;...调用FutureBuilder body: FutureBuilder( builder: _buildFutur
  • 【初始化请求数据】使用FutureBuilder组件即可完成需求,在future中定义请求数据函数即可,通过返回的snapshot组成返回的Widget刷新页面。 【搜索栏】通过获取的搜索数据对card的标题进行字符串匹配,筛选出符合...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 606
精华内容 242
关键字:

futurebuilder