Flutter之FutureBuilder的學習和使用
主要是學習FutureBuilder的使用,利用FutureBuilder來實現懶載入,並可以監聽載入過程的狀態 這個Demo是載入玩Android的一個頁面的列表資料

1.需求場景
經常有這些場景,就是先請求網路資料並顯示載入菊花,拿到資料後根據請求結果顯示不同的介面, 比如請求出錯就顯示error介面,響應結果裡面的列表資料為空的話,就顯示資料為空的介面,有資料的話, 就把列表資料載入到列表中進行顯示.
2.需要用到的控制元件
- 下拉重新整理RefreshIndicator,列表ListView,這裡不做過多介紹
- FutureBuilder:Flutter應用中的非同步模型,基於與Future互動的最新快照來構建自身的widget 官方文件: ofollow,noindex">docs.flutter.io/flutter/wid…
const FutureBuilder({ Key key, this.future,//獲取資料的方法 this.initialData, @required this.builder//根據快照的狀態,返回不同的widget }) : assert(builder != null), super(key: key); 複製程式碼
future就是一個定義的非同步操作,注意要帶上泛型,不然後面拿去snapshot.data的時候結果是dynamic的 snapshot就是future這個非同步操作的狀態快照,根據它的connectionState去載入不同的widget 有四種快照的狀態:
enum ConnectionState { //future還未執行的快照狀態 none, //連線到一個非同步操作,並且等待互動,一般在這種狀態的時候,我們可以載入個菊花 waiting, //連線到一個活躍的操作,比如stream流,會不斷地返回值,並還沒有結束,一般也是可以載入個菊花 active, //非同步操作執行結束,一般在這裡可以去拿取非同步操作執行的結果,並顯示相應的佈局 done, } 複製程式碼
下面的官方的例子。
FutureBuilder<String>( future: _calculation, // a previously-obtained Future<String> or null builder: (BuildContext context, AsyncSnapshot<String> snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: return Text('Press button to start.'); case ConnectionState.active: case ConnectionState.waiting: return Text('Awaiting result...'); case ConnectionState.done: if (snapshot.hasError) return Text('Error: ${snapshot.error}'); return Text('Result: ${snapshot.data}'); } return null; // unreachable }, ) 複製程式碼
3.實現思路,佈局方式
-
網路請求:利用Dio庫來請求玩Android的知識體系列表,api: www.wanandroid.com/tree/json
-
序列化json:利用json_serializable來解析返回的json資料
-
佈局:載入過程顯示CircularProgressIndicator,載入完成把資料顯示到ListView中, 載入為空或者加載出錯的話,顯示相應的提示頁面,並可以進行重試請求 一般。我們的列表頁面都是有下拉重新整理的功能,所以這裡就用RefreshIndicator來實現。
4.程式碼實現
import 'package:flutter/material.dart'; import 'package:dio/dio.dart'; import 'entity.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( // This is the theme of your application. // // Try running your application with "flutter run". You'll see the // application has a blue toolbar. Then, without quitting the app, try // changing the primarySwatch below to Colors.green and then invoke // "hot reload" (press "r" in the console where you ran "flutter run", // or simply save your changes to "hot reload" in a Flutter IDE). // Notice that the counter didn't reset back to zero; the application // is not restarted. primarySwatch: Colors.blue, ), home: FutureBuilderPage(), ); } } class FutureBuilderPage extends StatefulWidget { @override _FutureBuilderPageState createState() => _FutureBuilderPageState(); } class _FutureBuilderPageState extends State<FutureBuilderPage> { Future future; @override void initState() { // TODO: implement initState super.initState(); future = getdata(); } @override Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( title: new Text("知識體系"), actions: <Widget>[ new IconButton( icon: Icon( Icons.search, color: Colors.white, ), onPressed: null) ], ), body: buildFutureBuilder(), floatingActionButton: new FloatingActionButton(onPressed: () { setState(() { //測試futurebuilder是否進行沒必要的重繪操作 }); }), ); } FutureBuilder<List<Data>> buildFutureBuilder() { return new FutureBuilder<List<Data>>( builder: (context, AsyncSnapshot<List<Data>> async) { //在這裡根據快照的狀態,返回相應的widget if (async.connectionState == ConnectionState.active || async.connectionState == ConnectionState.waiting) { return new Center( child: new CircularProgressIndicator(), ); } if (async.connectionState == ConnectionState.done) { debugPrint("done"); if (async.hasError) { return new Center( child: new Text("ERROR"), ); } else if (async.hasData) { List<Data> list = async.data; return new RefreshIndicator( child: buildListView(context, list), onRefresh: refresh); } } }, future: future, ); } buildListView(BuildContext context, List<Data> list) { return new ListView.builder( itemBuilder: (context, index) { Data bean = list[index]; StringBuffer str = new StringBuffer(); for (Children children in bean.children) { str.write(children.name + ""); } return new ListTile( title: new Text(bean.name), subtitle: new Text(str.toString()), trailing: new IconButton( icon: new Icon( Icons.navigate_next, color: Colors.grey, ), onPressed: () {}), ); }, itemCount: list.length, ); } //獲取資料的邏輯,利用dio庫進行網路請求,拿到資料後利用json_serializable解析json資料 //並將列表的資料包裝在一個future中 Future<List<Data>> getdata() async { debugPrint("getdata"); var dio = new Dio(); Response response = await dio.get("http://www.wanandroid.com/tree/json"); Map<String, dynamic> map = response.data; Entity entity = Entity.fromJson(map); return entity.data; } //重新整理資料,重新設定future就行了 Future refresh() async { setState(() { future = getdata(); }); } } 複製程式碼
5.注意的問題和踩坑
- 防止FutureBuilder進行不必要的重繪:這裡我採用的方法,是將getData()賦值給一個future的成員變數, 用它來儲存getData()的結果,以避免不必要的重繪 參考文章: blog.csdn.net/u011272795/…
- FutureBuilder和RefreshIndicator的巢狀問題,到底誰是誰的child,這裡我是把RefreshIndicator作為FutureBuilder 的孩子。如果將RefreshIndicator放在外層,FutureBuilder作為child的話,當RefreshIndicator呼叫onrefreh重新整理資料並用 setState()去更新介面的時候,那FutureBuilder也會再次經歷生命週期,所以導致獲取資料的邏輯會被走兩遍