首先道個歉,最近公司很忙,又遇上十一假期,因此鴿了將近半個月。git
不過,後續仍是會每週最少更新兩篇的!github
那提及網絡請求的控件,咱們首先是否是會想起 FutureBuilder
?微信
FutureBuilder
給咱們封裝好了網絡請求中的各類狀態。markdown
若是沒有了解過,那麼能夠看我這篇文章:Flutter - FutureBuilder 異步UI神器。網絡
這篇文章是早期寫的,有些地方寫的有些問題,但不重要!主要了解一下 FutureBuilder
的狀態就能夠了。異步
本篇文章中只是提供一種思路,歡迎一塊兒探討,也歡迎不吝賜教!async
效果以下。ide
首先是沒有開啓服務的狀況:工具
能夠看到所有都是錯誤的信息,ui
而後開啓服務:
那既然是網絡請求,那首先咱們要定義一個通用的網絡請求方法。
每一家後臺 API 的風格都不同,有的是 RSETful,有的是咱們最熟悉的 GET、POST。
這裏就以 GET 爲例,API 接口爲 GitHub - 網易雲音樂 Node.js API service。
網絡請求使用的是 Dio,先建立一個 NetUtils.dart
。
初始化代碼:
static Dio _dio;
static void init() async {
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
CookieJar cj = PersistCookieJar(dir: tempPath);
_dio = Dio(BaseOptions(baseUrl: 'http://127.0.0.1:3000'))
..interceptors.add(CookieManager(cj))
..interceptors.add(LogInterceptor(responseBody: true, requestBody: true));
}
複製代碼
在 runApp 前面調用即完成初始化。
接着定義一個通用的網絡請求:
static Future<Response> _get(
BuildContext context,
String url, {
Map<String, dynamic> params,
}) async {
Loading.showLoading(context);
try {
return await _dio.get(url, queryParameters: params);
} on DioError catch (e) {
if (e.response is Map) {
return Future.value(e.response);
} else {
return Future.error(0);
}
} finally {
Loading.hideLoading(context);
}
}
複製代碼
這裏代碼很簡單,方法須要傳入三個參數:
方法內部咱們捕獲了 DioError
,而後判斷接口是否還返回了正常的內容。
例如:狀態碼不爲2xx,可是仍然返回了數據,這樣 Dio 是會拋出 DioError
的,須要咱們本身捕獲來處理。
若是返回了正常的數據,那咱們仍是返回回去,若是不是正常的數據,則直接拋出 Future.error(0)
。
使用該通用方法:
/// 新碟上架
static Future<AlbumData> getAlbumData(
BuildContext context, {
Map<String, dynamic> params = const {
'offset': 0,
'limit': 10,
},
}) async {
var response = await _get(context, '/top/album', params: params);
return AlbumData.fromJson(response.data);
}
複製代碼
咱們就能夠像這樣來使用剛纔定義好的方法,也方便咱們後續定義一個通用的 FutureBuilder
。
咱們從最開始的圖中明顯能看出來的,實際上是有三個功能:
然鵝,細心的同窗也發現問題了。
咱們在網絡請求中添加了一個 Loading,並且須要一個 BuildContext。
咱們都知道,是不能在 initState()
方法中去使用這個 BuildContext 的。
因此,咱們還要進行一個 第一幀回調:
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((call) {
_request();
});
}
複製代碼
這樣就完成了咱們的需求調研。
說的是一個通用的網絡請求控件,其實就是把 FutureBuilder
封裝一層。
可是,這裏也有一個問題:
咱們在最開始定義網絡請求工具類的時候,每個網絡請求都是一個方法,而每一個方法中都有或者沒有參數。
咱們也知道,FutureBuilder
須要傳入一個 Future,那這可怎麼辦?
並且咱們不能在使用該控件的時候調用網絡請求方法,由於網絡請求中封裝了一個 Loading,這個 Loading 須要 BuildContext
。
既然如此,那咱們只能傳入方法(Function)了:
typedef ValueWidgetBuilder<T> = Widget Function(
BuildContext context,
T value,
);
final ValueWidgetBuilder<T> builder;
final Function futureFunc;
final Map<String, dynamic> params;
CustomFutureBuilder({
@required this.futureFunc,
@required this.builder,
this.params,
});
複製代碼
這樣,咱們就能夠在 第一幀回調 中來調用該網絡請求了,這樣一箭雙鵰:
既不用在使用該控件的時候調用方法,又避免了 Loading 使用 BuildContext 報錯的問題。
Future<T> _future;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((call) {
_request();
});
}
void _request() {
setState(() {
if (widget.params == null)
_future = widget.futureFunc(context);
else
_future = widget.futureFunc(context, params: widget.params);
});
}
複製代碼
首先咱們定義了一個 Future,而後在 第一幀回調 中初始化該 Future 就能夠了。
這就須要咱們封裝好的網絡請求和 FutureBuilder
有一個互動了,
網絡請求的邏輯以下:
這樣正好就能夠對應 FutureBuilder
的幾種狀態:
ConnectionState.none
、ConnectionState.waiting
ConnectionState.active
ConnectionState.done
snapshot.hasData
snapshot.hasError
瞭解這些以後,咱們就能夠寫出代碼:
Widget build(BuildContext context) {
return _future == null
? Container(
alignment: Alignment.center,
height: ScreenUtil().setWidth(200),
child: CupertinoActivityIndicator(),
)
: FutureBuilder(
future: _future,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return Container(
alignment: Alignment.center,
height: ScreenUtil().setWidth(200),
child: CupertinoActivityIndicator(),
);
case ConnectionState.done:
if (snapshot.hasData) {
return widget.builder(context, snapshot.data);
} else if (snapshot.hasError) {
return NetErrorWidget(
callback: () {
_request();
},
);
}
}
return Container();
},
);
}
複製代碼
首先判斷 _future 是否爲 null,若是爲空,那麼則表示尚未初始化該 Future,
我的建議這個時候返回本身定義好的加載中 Widget,由於後續在網絡請求中的時候也返回該 Widget,這樣不會顯得亂。
而後在 ConnectionState.done
中判斷是否存在數據,若是有的話,就顯示傳進來的 Widget。
若是返回錯誤,則返回錯誤的 Widget。
這個邏輯其實很簡單,在我最開始說的文章中有講解一部分。
那就是何時 FutureBuilder 會從新建立?
@override
void didUpdateWidget(FutureBuilder<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.future != widget.future) {
if (_activeCallbackIdentity != null) {
_unsubscribe();
_snapshot = _snapshot.inState(ConnectionState.none);
}
_subscribe();
}
}
複製代碼
能夠很清晰的看到,在兩次 Future 不同的狀況下會從新走一遍流程。不然是不會走的。
而咱們在上面也已經定義好了,由於傳進來的是 Function 和 Params,咱們能夠隨時從新建立該 Future:
void _request() {
setState(() {
if (widget.params == null)
_future = widget.futureFunc(context);
else
_future = widget.futureFunc(context, params: widget.params);
});
}
複製代碼
錯誤 Widget 的點擊事件寫成這個就 ok 了,這樣就從新建立了該 FutureBuilder
,也就是從新請求了。
代碼的話,我就不傳上去了,由於這個只適用於一部分。
我這裏只是提供一種思路,我的以爲仍是不錯的。
若是有什麼想法的話,歡迎一塊兒探討,不吝賜教!
另我我的建立了一個「Flutter 交流羣」,能夠添加我我的微信 「17610912320」來入羣。
推薦閱讀:
Flutter | WReorderList 一個能夠指定兩個item互換位置的組件
Flutter | 如何實現一個水波紋擴散效果的 Widget