【 源码之间 - Flutter 】 FutureBuilder源码分析

一、前言:

1.先简单说下源码之间
  • 1 】: 源码之间张风捷特烈bilibili的直播间,版权所有。
  • 2 】: 源码之间直播和产出的所有视频资源都将是免费的,允许被录制加工随意传播
  • 3 】: 禁止使用源码之间的视频资源做任何盈利行为的是事,违者必究。
  • 4 】: 源码之间的直播内容主要是源码的分析,也可能是分享和研究某一编程问题。

FutureBuilder源码分析: 录播视屏: www.bilibili.com/video/BV1We…
示例demo的代码贴在文尾,可以自己跑跑,调试看看。


2.示例demo效果

主要就是请求网络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
  • FutureBuilder是一个StatefulWidget,主要有三个成员变量:

1】. futureFuture 类型----待执行的异步任务
2】. builderAsyncWidgetBuilder类型----异步组件构造器
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.withData(ConnectionState.none, widget.initialData);
  _subscribe();
}
复制代码

3. AsyncSnapshot状态量类

所以先看一下_snapshot对象所对应的AsyncSnapshot
它核心是三个成员变量,记录状态、数据和异常情况
并且提供一些命名构造方便创建对象和一些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.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((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>(
  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((T data) {
      if (_activeCallbackIdentity == callbackIdentity) {
        setState(() {
          _snapshot = AsyncSnapshot.withData(ConnectionState.done, data);
        });
      }
    }, onError: (Object error) {
      if (_activeCallbackIdentity == callbackIdentity) {
        setState(() {
          _snapshot = AsyncSnapshot.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 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 未允禁转
我的公众号:编程之王
联系我--邮箱:[email protected] --微信:zdl1994328
~ END ~


附录: demo 源码

1. api.dart
import 'dart:convert';

/// create by 张风捷特烈 on 2020/5/9
/// contact me by email [email protected]
/// 说明:
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 [email protected]
/// 说明:

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), ), ), ); } 复制代码

你可能感兴趣的:(【 源码之间 - Flutter 】 FutureBuilder源码分析)