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 case
List<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 connection
ConnectionState
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
_snapshot
In this bottle, andsetState
- such
_snapshot
After 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