轉載註明出處:https://blog.csdn.net/skysukai
1、背景
項目使用Flutter來做,官方已經已經有Dio提供了支持。比如下面這段代碼,在理想情況下,拿到response就可以直接解析json數據了。但一般而言,服務器返回的數據都不會這麼簡單,所以,我們需要做的就是二次封裝Dio。
Response response = await dio.post(url, data: params);
有關Dio的科普,傳送門.
2、服務器數據結構
單個數據結構如下:
{
"result": {
"apkName": "string",
"appInfo": "string",
"appNam": "string",
"appUrl": "string",
},
"resultInfo": {
"resultCode": "string",
"resultMsg": "string"
}
}
數據結構列表如下:
{
"result": {
"count": 0,
"data": [
{
"apkName": "string",
"appInfo": "string",
"appNam": "string",
"appUrl": "string",
}
],
"pagenum": 0,
"pagesize": 0
},
"resultInfo": {
"resultCode": "string",
"resultMsg": "string"
}
}
如果要直接解析這些數據顯然是不行的。考慮統一封裝,直接給出代碼如下:
class ResultInfo {
String resultCode;
String resultMsg;
}
class RequestResult {
ResultInfo resultInfo;
dynamic result;
}
將Response的成員變量data申明爲ResquestResult,通過post請求得到的數據應該都是RequestResult類型的了。flutter同時提供了jsonserializable
插件來爲類進行json序列化和反序列化,大大提高了效率。使用jsonserializable
來生成相關代碼:
@JsonSerializable()
class ResultInfo {
String resultCode;
String resultMsg;
ResultInfo({this.resultCode, this.resultMsg});
factory ResultInfo.fromJson(Map<String, dynamic> json) => _$ResultInfoFromJson(json);
Map<String, dynamic> toJson() => _$ResultInfoToJson(this);
@override
String toString() {
return toJson().toString();
}
}
@JsonSerializable()
class RequestResult {
ResultInfo resultInfo;
dynamic result;
RequestResult({this.resultInfo, this.result});
factory RequestResult.fromJson(Map<String, dynamic> json) => _$RequestResultFromJson(json);
Map<String, dynamic> toJson() => _$RequestResultToJson(this);
@override
String toString() {
return toJson().toString();
}
}
3、網絡請求的統一封裝
將有關網絡請求的操作封裝到NetworkManager
裏,設置全局單例,方便調用。
3.1 基礎設置
NetworkManager._internal(String baseUrl) {
_dio = Dio(BaseOptions(
baseUrl: baseUrl,
connectTimeout: 5000,
receiveTimeout: 3000,
));
//請求結果需進行json反序列化
(_dio.transformer as DefaultTransformer).jsonDecodeCallback = _parseResponse;
//添加Interceptor,打印日誌,方便調試
_dio.interceptors.add(NetworkLogInterceptor(requestBody: false,
responseHeader: false,
responseBody: true));
}
3.2 請求結果json反序列化
由於我們自己定義了數據結構,在得到請求結果的時候需要加上反序列化函數,否則拋出異常:
Future<RequestResult> _parseResponse(String jsonString) {
return compute(_parseResult, jsonString);
}
static RequestResult _parseResult(String jsonString) {
return RequestResult.fromJson(JsonCodec().decode(jsonString));
}
3.3 代碼主體
以post請求爲例,給出代碼
/**
** post方法
** onRequestSuccess:請求成功回調,回調給調用方
** onRequestFailure:請求失敗回調,回調給調用方
** ParseResult:數據解析結果,回調給調用方
**/
typedef ParseResult = dynamic Function(Map<String, dynamic> json);
void post(String path, Function onRequestSuccess, Function onRequestFailure, {
data,
Map<String, dynamic> queryParameters,
ParseResult parseResult,
Options options,
CancelToken cancelToken,
ProgressCallback onSendProgress,
ProgressCallback onReceiveProgress
}) async {
try {
Response<RequestResult> response = await _dio.post(path,
data: data,
options: _checkOptions(options),
queryParameters: queryParameters,
cancelToken: cancelToken,
onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress);
if (response.data.resultInfo.resultCode == "200") {
if (onRequestSuccess != null) {
onRequestSuccess(parseResult != null && (response.data.result is Map<String, dynamic>)
? parseResult(response.data.result)
: response.data.result);
}
} else {
if (onRequestFailure != null) {
onRequestFailure(int.parse(response.data.resultInfo.resultCode),
response.data.resultInfo.resultMsg);
}
}
} on DioError catch(e) {
if (onRequestFailure != null) {
onRequestFailure(e.response != null ? e.response.statusCode : e.type.index,
"DioError [${e.type}]: " + (e.message ?? _dioErrorType[e.type]));
}
}
}
4、網絡請求的具體實現
本段以視頻列表爲例,說明請求的具體過程。
4.1 服務器返回數據結構
{
"result": {
"count": 0,
"data": [
{
"iconUrl": "string",
"likeCount": 0,
"liked": false,
"orientation": 0,
"playUrl": "string",
"publishAvatar": "string",
"publishNick": "string",
"publishPhotosCount": 0,
"publishUid": 0,
"publishVideosCount": 0,
"published": false,
"reviewCount": 0,
"topicId": 0,
"topicName": "string",
"type": 0,
"vid": 0
}
],
"pagenum": 0,
"pagesize": 0
},
"resultInfo": {
"resultCode": "string",
"resultMsg": "string"
}
}
我們需先將返回的data定義爲一個類,進行json序列化及反序列化。
4.2 客戶端數據構造
4.2.1 請求數據構造
發起請求時,需要先將請求的數據做一次json序列化,主要是請求數量、頁碼等服務器規定的參數。
@JsonSerializable()
class VideoListParam {
int topicId;
String udId;
@JsonKey(name: "pagenum")
int pageNum;
@JsonKey(name: "pagesize")
int pageSize;
VideoListParam({this.topicId, this.udId, this.pageNum, this.pageSize});
factory VideoListParam.fromJson(Map<String, dynamic> json) => _$VideoListParamFromJson(json);
Map<String, dynamic> toJson() => _$VideoListParamToJson(this);
}
4.2.2 返回單個數據構造
data裏面的item命名爲VideoInfo
使用jsonserializable
生成相關代碼:
@JsonSerializable()
class VideoInfo {
@JsonKey(defaultValue: 0)
int format;
String description;
@JsonKey(defaultValue: false, nullable: true)
bool followed;
@JsonKey(defaultValue: 0, nullable: true)
int likeCount;
@JsonKey(defaultValue: false, nullable: true)
bool liked;
String playUrl;
String publishAvatar;
String publishNick;
@JsonKey(defaultValue: 0, nullable: true)
int publishUid;
@JsonKey(defaultValue: 0, nullable: true)
int publishVideosCount;
@JsonKey(defaultValue: 0, nullable: true)
int publishPhotosCount;
@JsonKey(defaultValue: 0, nullable: true)
int reviewCount;
@JsonKey(defaultValue: 0, nullable: true)
int topicId;
String topicName;
@JsonKey(defaultValue: 0, nullable: true)
int vid;
@JsonKey(defaultValue: 0, nullable: true)
int orientation;
@JsonKey(defaultValue: true, nullable: true)
bool published;
VideoInfo({ this.format, this.description, this.followed, this.likeCount, this.liked, this.playUrl, this.publishAvatar, this.publishNick, this.publishUid, this.publishVideosCount, this.publishPhotosCount, this.reviewCount, this.topicId, this.topicName, this.vid, this.orientation, this.published});
factory VideoInfo.fromJson(Map<String, dynamic> json) => _$VideoInfoFromJson(json);
Map<String, dynamic> toJson() => _$VideoInfoToJson(this);
}
4.2.3 返回數據列表構造
@JsonSerializable()
class VideoListResult {
int count;
@JsonKey(name: "pagenum")
int pageNum;
@JsonKey(name: "pagesize")
int pageSize;
List<VideoInfo> data;
VideoListResult({this.count, this.pageNum, this.pageSize});
factory VideoListResult.fromJson(Map<String, dynamic> json) => _$VideoListResultFromJson(json);
Map<String, dynamic> toJson() => _$VideoListResultToJson(this);
}
4.3 發起請求
/**
**onSuccess:請求成功回調
**onFailure:請求失敗回調
**/
typedef RequestSuccess = void Function(dynamic result);
typedef RequestFailure = void Function(int code, String desc);
void loadFromServer({RequestSuccess onSuccess, RequestFailure onFailure}) {
int pageNum = 1;
RequestSuccess requestSuccess = (dynamic result) {
VideoListResult videoListResult = result as VideoListResult;
if (!mounted) {
return;
}
setState(() {
……
});
};
RequestFailure requestFailure = (int code, String desc) {
setState(() {
……
});
Log.d("result", "$code, $desc");
};
//請求參數
VideoListParam videoListParam =
VideoListParam(pageNum: pageNum, pageSize: pageSize);
Request.getVideoList(videoListParam, requestSuccess, requestFailure);
}
class Request {
static void getVideoList(VideoListParam param, RequestSuccess onRequestSuccess, RequestFailure onRequestFailure) async {
NetworkManager().post(getVideoListUrl(),
onRequestSuccess,
onRequestFailure,
queryParameters: param.toJson(),
parseResult: (Map<String, dynamic> json) => VideoListResult.fromJson(json));
}
}
5 、總結
Flutter下的網絡請求總體來說,還是很好用的。各種回調也不像java那樣使用了接口,而是直接用typedef
關鍵字來達到了接口相同的目的。希望本文對你有所幫助。
相關參考:https://medium.com/flutter-community/parsing-complex-json-in-flutter-747c46655f51
相關參考:https://www.jianshu.com/p/cb72e2f5df1c
相關參考:https://www.jianshu.com/p/1352351c7d08