【 源碼之間 - Flutter 】 FutureBuilder源碼分析

1、前言:

1.先簡單說下源碼之間
  • 1 】: 源碼之間張風捷特烈bilibili的直播間,版權全部。
  • 2 】: 源碼之間直播和產出的全部視頻資源都將是免費的,容許被錄製加工隨意傳播
  • 3 】: 禁止使用源碼之間的視頻資源作任何盈利行爲的是事,違者必究。
  • 4 】: 源碼之間的直播內容主要是源碼的分析,也多是分享和研究某一編程問題。

FutureBuilder源碼分析: 錄播視屏: www.bilibili.com/video/BV1We…
示例demo的代碼貼在文尾,能夠本身跑跑,調試看看。android


2.示例demo效果

主要就是請求網絡api,返回數據,展業界面。根據不一樣的狀態顯示不一樣的界面。git

加載中 加載完成 加載失敗

1、示例demo詳述:

1.關於異步請求

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}';
  }
}
複製代碼

2. FutureBuilder的使用

先定義異步任務和當前頁碼,在使用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,
      ),
    );
  }

複製代碼

2、FutureBuilder源碼分析

1. FutureBuilder組件類
  • FutureBuilder是一個具備泛型T的類,T表明異步的數據類型,這裏也就是List<Article>
  • FutureBuilder是一個StatefulWidget,主要有三個成員變量:

1】. futureFuture<T> 類型----待執行的異步任務
2】. builderAsyncWidgetBuilder<T>類型----異步組件構造器
3】. initialDataT 類型----初始數據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;
複製代碼

2. _FutureBuilderState狀態類
  • StatefulWidget主要依賴State進行構建組件,因此這裏重要的是_FutureBuilderState

其中有兩個成員變量_activeCallbackIdentity_snapshotbash

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();
}
複製代碼

3. AsyncSnapshot狀態量類

因此先看一下_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();
}
複製代碼

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<List<Article>> 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<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的能力就是根據異步任務的執行狀況,向外界暴露出狀態方便構建不一樣的界面


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<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}';
  }
}
複製代碼

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

複製代碼
相關文章
相關標籤/搜索