首先道个歉,最近公司很忙,又遇上十一假期,因此鸽了将近半个月。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