I. Preface:

The main is to request network API, return data, display industry interface. Display different interfaces according to different states.

In the load loaded Load failed

First, sample demo details:

1. About asynchronous requests

FutureBuilder requires an asynchronous task as a construction entry

Fetch article list via wanAndroid API, api.fetch (int page)

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; }}Copy the code

Articles are represented by three fields

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'] ?? Json ['link']?? '', time: json['niceDate'] ?? ' ', a); } @override String toString() { return 'Article{title: $title, url: $url, time: $time}'; }}Copy the code

2. Use of FutureBuilder

Define the asynchronous task and the current page number before building the component using FutureBuilder. The full code is at the end of the article.

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

Second, FutureBuilder source code analysis

1. FutureBuilder component class
  • FutureBuilder is a class with the generic T, which stands for asynchronous data type, in this caseList<Article>
  • FutureBuilder is a StatefulWidget with three main member variables:

1】. Future: Future

type —- Asynchronous task to be executed

2】. Builder: AsyncWidgetBuilder

type —- asynchronous component constructor 3】. InitialData: T type —- initialData

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;Copy the code

2. _FutureBuilderState Status class
  • StatefulWidget relies primarily on State to build components, so it’s important here_FutureBuilderState

There are two member variables, _activeCallbackIdentity and _snapshot

class _FutureBuilderState<T> extends State<FutureBuilder<T>> {

  Object _activeCallbackIdentity;
  AsyncSnapshot<T> _snapshot;
Copy the code

Initialize _snapshot in _FutureBuilderState#initState

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

3. AsyncSnapshot state quantity class

So take a look at the AsyncSnapshot

class that the _snapshot object corresponds to

At its core are three member variables that record state, data, and exceptions and provide naming constructs to create objects and get methods to use

final ConnectionState connectionState; Final T data; Data final Object error; Abnormal #Copy the code

Basically, it's a bottle with three things in it.


  • Also important is the state of the connectionConnectionState
Enum ConnectionState {none, # initial waiting, # initial waiting, # Stream active but not finished done, # end}Copy the code

Now look back at the initialization of _snapshot in _FutureBuilderState#initState: The connection state is None, the data is the initial data provided, or null if there is none

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

4. Core logic of FutureBuilder
  • _snapshot initialization is complete, and then execute_subscribe()This is the soul of FutureBuilder

If widget.future is not empty, the callbackIdentity is created and the asynchronous task is started

Then set the state of _snapshot to connectionstate.waiting

void _subscribe() { if (widget.future ! = null) { final Object callbackIdentity = Object(); _activeCallbackIdentity = callbackIdentity; Widget.future. Then <void>((T data) {// Zan... }, onError: (Object error) {// An error... }); _snapshot = _snapshot.inState(ConnectionState.waiting); }}Copy the code

  • InitState is done, followed by a call to State#build

Here is the builder method used to create the component externally, which calls back _snapshot for external use

The _snapshot state is waiting;

@override
Widget build(BuildContext context) => widget.builder(context, _snapshot);
Copy the code

Create the component in the outside world using the _builderList method

body: FutureBuilder<List<Article>>(
  future: _articles,
  builder: _builderList,
),
Copy the code

Based on the snapshot of the callback, you can decide which screen to return to

For example, if it is now connectionstate. waiting, you can return to the loading component

Widget _builderList( BuildContext context, AsyncSnapshot<List<Article>> snapshot) { switch (snapshot.connectionState) { //... //<-- current waiting print('------- connectionstate. waiting---------'); return _buildLoading(); break; }}Copy the code

  • Then, after the asynchronous event completes, the function in THEN is called back, which is here in the source code
  • You can see that the callback will place the asynchronously returned data in_snapshotIn this bottle, andsetState
  • such_snapshotAfter the update, the build method is re-executed and the external callback is called back_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); }}Copy the code

This jumps to Connectionstate. done and returns the list component

Snapshot. hasError is true when an exception occurs so that the error interface can be built

Widget _builderList( BuildContext context, AsyncSnapshot<List<Article>> snapshot) { if (snapshot.hasError) { return _buildError(snapshot.error); } switch (snapshot.connectionState) { //... Other 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]), )), ); }Copy the code

So FutureBuilder’s ability is to expose state to the outside world to build different interfaces based on how asynchronous tasks are performing.


5. Behavior of _FutureBuilderState when the parent component is refreshed
  • When the plus sign is clicked, the asynchronous method is updated, the next page of data is retrieved, and the parent component executes setState
void _doAdd() {
  setState(() {
    _page++;
    _articles = Api.fetch(_page);
  });
}
Copy the code

Instead of going State#initState, the didUpdateWidget is used

When two asynchronous tasks are different, we cancel the first one and then execute _subscribe, and then we have the same thing.

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

Cancelling is also easy, just empty the logo.

void _unsubscribe() {
  _activeCallbackIdentity = null;
}
Copy the code

FutureBuilder source code is only these, see it is not very difficult. In essence, setState is used to update sub-components.


The end of the

Welcome to Star and pay attention to the development of FlutterUnit. Let’s join hands and become a member of Unit.

In addition, I have a Flutter wechat communication group. You are welcome to join and discuss Flutter issues together. I look forward to exchanging ideas with you.

@ZhangFengjietele 2020.05.10 not allowed to transfer

My official number: King of programming contact me – email :[email protected] – wechat :zdl1994328 ~ END ~


Appendix:The demo source code

1. api.dart
import 'dart:convert'; Contact me by email [email protected] // 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'] ?? Json ['link']?? '', time: json['niceDate'] ?? ' ', a); } @override String toString() { return 'Article{title: $title, url: $url, time: $time}'; }}Copy the code

2. main.dart
import 'package:flutter/cupertino.dart'; 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<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: <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), ), ), ); }Copy the code