Flutter下的通用網絡框架

轉載註明出處: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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章