blog

FlutterのウェブリクエストライブラリDioをラッピングする

デモアドレス -.itは、ネットワークリクエストのいくつかの利点をカプセル化します: er、公開パラメータ、デバッグを容易にする暗号化ルールなどのリクエストパラメータの統一された設定を容易にするために...

Feb 14, 2020 · 8 min. read
シェア

ウェブリクエストをカプセル化することには、いくつかの利点があります:

  1. ヘッダ、公開パラメータ、暗号化ルールなどのリクエストパラメータの統一的な設定を容易にします。
  2. 簡単なデバッグ、詳細なログ印刷情報
  3. コードパフォーマンスの最適化、新しいオブジェクトのスパムの回避、グローバルシングレットンの構築
  4. 必要なレスポンスデータのみを公開することでリクエストステップを簡素化し、不正なレスポンスに対するコールバックを統一します。
  5. 解析プロセスを簡素化するインターフェース・データのベース・クラス・カプセル化
  6. 非侵入的で柔軟なリクエストロード設定

ロード自動化のリクエスト

コードを記述することなく、パラメータを渡すだけでLoadingの効果をリクエストに追加できます!

 var params = DataHelper.getBaseMap();
 params.clear();
 params["apikey"] = "0df993c66c0c636e29ecbb5344252a4a";
 params["start"] = "0";
 params["count"] = "10";
//withLoading省略可能、デフォルトが追加される、より簡潔になる
 ResultData res = await HttpManager.getInstance()
 .get(Address.TEST_API, params: params, withLoading: true);

明確で包括的なログ印刷

パケットキャプチャを追加設定する必要がなくなり、インターフェイスのデバッグ効率が大幅に向上します!

以下は、主要なソースコードを通してカプセル化のプロセスを紹介したものです。

HttpManager

グローバルシングルトンの構築、リクエストパラメータの設定、汎用GETPOSTの設定、baseUrl切り替えのサポート

import 'package:dio/dio.dart';
import 'package:flutter_net/code.dart';
import 'package:flutter_net/dio_log_interceptor.dart';
import 'package:flutter_net/loading_utils.dart';
import 'response_interceptor.dart';
import 'result_data.dart';
import 'address.dart';
class HttpManager {
 static HttpManager _instance = HttpManager._internal();
 Dio _dio;
 static const CODE_SUCCESS = 200;
 static const CODE_TIME_OUT = -1;
 factory HttpManager() => _instance;
 ///一般的なグローバル・シングルトンで、最初に使われたときに初期化される。
 HttpManager._internal({String baseUrl}) {
 if (null == _dio) {
 _dio = new Dio(
 new BaseOptions(baseUrl: Address.BASE_URL, connectTimeout: 15000));
 _dio.interceptors.add(new DioLogInterceptor());
// _dio.interceptors.add(new PrettyDioLogger());
 _dio.interceptors.add(new ResponseInterceptors());
 }
 }
 static HttpManager getInstance({String baseUrl}) {
 if (baseUrl == null) {
 return _instance._normal();
 } else {
 return _instance._baseUrl(baseUrl);
 }
 }
 //特定のドメイン名を指定する
 HttpManager _baseUrl(String baseUrl) {
 if (_dio != null) {
 _dio.options.baseUrl = baseUrl;
 }
 return this;
 }
 //一般的な要望、デフォルトのドメイン名
 HttpManager _normal() {
 if (_dio != null) {
 if (_dio.options.baseUrl != Address.BASE_URL) {
 _dio.options.baseUrl = Address.BASE_URL;
 }
 }
 return this;
 }
 ///一般的なGETリクエスト
 get(api, {params, withLoading = true}) async {
 if (withLoading) {
 LoadingUtils.show();
 }
 Response response;
 try {
 response = await _dio.get(api, queryParameters: params);
 if (withLoading) {
 LoadingUtils.dismiss();
 }
 } on DioError catch (e) {
 if (withLoading) {
 LoadingUtils.dismiss();
 }
 return resultError(e);
 }
 if (response.data is DioError) {
 return resultError(response.data['code']);
 }
 return response.data;
 }
 ///一般的なPOSTリクエスト
 post(api, {params, withLoading = true}) async {
 if (withLoading) {
 LoadingUtils.show();
 }
 Response response;
 try {
 response = await _dio.post(api, data: params);
 if (withLoading) {
 LoadingUtils.dismiss();
 }
 } on DioError catch (e) {
 if (withLoading) {
 LoadingUtils.dismiss();
 }
 return resultError(e);
 }
 if (response.data is DioError) {
 return resultError(response.data['code']);
 }
 return response.data;
 }
}
ResultData resultError(DioError e) {
 Response errorResponse;
 if (e.response != null) {
 errorResponse = e.response;
 } else {
 errorResponse = new Response(statusCode: 666);
 }
 if (e.type == DioErrorType.CONNECT_TIMEOUT ||
 e.type == DioErrorType.RECEIVE_TIMEOUT) {
 errorResponse.statusCode = Code.NETWORK_TIMEOUT;
 }
 return new ResultData(
 errorResponse.statusMessage, false, errorResponse.statusCode);
}

レスポンス基底クラス

デフォルト200 isSuccessがtrueの場合、レスポンスはresponse.data、dataに代入

class ResultData {
 var data;
 bool isSuccess;
 int code;
 var headers;
 ResultData(this.data, this.isSuccess, this.code, {this.headers});
}

Api

リクエストの一元管理

class Api {
 ///サンプルリクエスト
 static request(String param) {
 var params = DataHelper.getBaseMap();
 params['param'] = param;
 return HttpManager.getInstance().get(Address.TEST_API, params);
 }
}

公開パラメータ、暗号化など

class DataHelper{
 static SplayTreeMap getBaseMap() {
 var map = new SplayTreeMap<String, dynamic>();
 map["platform"] = AppConstants.PLATFORM;
 map["system"] = AppConstants.SYSTEM;
 map["channel"] = AppConstants.CHANNEL;
 map["time"] = new DateTime.now().millisecondsSinceEpoch.toString();
 return map;
 }
 
 static string2MD5(String data) {
 var content = new Utf8Encoder().convert(data);
 var digest = md5.convert(content);
 return hex.encode(digest.bytes);
 }
}

アドレスの設定

便利なアドレス管理

class Address {
 static const String TEST_API = "test_api";
}

レスポンスインターセプター

初期データカプセル化のための正しいレスポンスデータのフィルタリング

import 'package:dio/dio.dart';
import 'package:exchange_flutter/common/net/code.dart';
import 'package:flutter/material.dart';
import '../result_data.dart';
class ResponseInterceptors extends InterceptorsWrapper {
 @override
 onResponse(Response response) {
 RequestOptions option = response.request;
 try {
 if (option.contentType != null &&
 option.contentType.primaryType == "text") {
 return new ResultData(response.data, true, Code.SUCCESS);
 }
 ///一般的には、エラーメッセージを保持するために200, 500のケースに対処するだけでよい。
 if (response.statusCode == 200 || response.statusCode == 201) {
 int code = response.data["code"];
 if (code == 0) {
 return new ResultData(response.data, true, Code.SUCCESS,
 headers: response.headers);
 } else if (code == 100006 || code == 100007) {
 } else {
 Fluttertoast.showToast(msg: response.data["msg"]);
 return new ResultData(response.data, false, Code.SUCCESS,
 headers: response.headers);
 }
 }
 } catch (e) {
 print(e.toString() + option.path);
 return new ResultData(response.data, false, response.statusCode,
 headers: response.headers);
 }
 return new ResultData(response.data, false, response.statusCode,
 headers: response.headers);
 }
}

ログインターセプター

リクエスト・パラメータとリターン・パラメータの印刷


import 'package:dio/dio.dart';
///ログ・インターセプター
class DioLogInterceptor extends Interceptor {
 @override
 Future onRequest(RequestOptions options) async {
 String requestStr = "
==================== REQUEST ====================
"
 "- URL:
${options.baseUrl + options.path}
"
 "- METHOD: ${options.method}
";
 requestStr += "- HEADER:
${options.headers.mapToStructureString()}
";
 final data = options.data;
 if (data != null) {
 if (data is Map)
 requestStr += "- BODY:
${data.mapToStructureString()}
";
 else if (data is FormData) {
 final formDataMap = Map()
 ..addEntries(data.fields)
 ..addEntries(data.files);
 requestStr += "- BODY:
${formDataMap.mapToStructureString()}
";
 } else
 requestStr += "- BODY:
${data.toString()}
";
 }
 print(requestStr);
 return options;
 }
 @override
 Future onError(DioError err) async {
 String errorStr = "
==================== RESPONSE ====================
"
 "- URL:
${err.request.baseUrl + err.request.path}
"
 "- METHOD: ${err.request.method}
";
 errorStr +=
 "- HEADER:
${err.response.headers.map.mapToStructureString()}
";
 if (err.response != null && err.response.data != null) {
 print('  ${err.type.toString()}');
 errorStr += "- ERROR:
${_parseResponse(err.response)}
";
 } else {
 errorStr += "- ERRORTYPE: ${err.type}
";
 errorStr += "- MSG: ${err.message}
";
 }
 print(errorStr);
 return err;
 }
 @override
 Future onResponse(Response response) async {
 String responseStr =
 "
==================== RESPONSE ====================
"
 "- URL:
${response.request.uri}
";
 responseStr += "- HEADER:
{";
 response.headers.forEach(
 (key, list) => responseStr += "
 " + ""$key" : "$list",");
 responseStr += "
}
";
 responseStr += "- STATUS: ${response.statusCode}
";
 if (response.data != null) {
 responseStr += "- BODY:
 ${_parseResponse(response)}";
 }
 printWrapped(responseStr);
 return response;
 }
 void printWrapped(String text) {
 final pattern = new RegExp('.{1,800}'); // 800 is the size of each chunk
 pattern.allMatches(text).forEach((match) => print(match.group(0)));
 }
 String _parseResponse(Response response) {
 String responseStr = "";
 var data = response.data;
 if (data is Map)
 responseStr += data.mapToStructureString();
 else if (data is List)
 responseStr += data.listToStructureString();
 else
 responseStr += response.data.toString();
 return responseStr;
 }
}
extension Map2StringEx on Map {
 String mapToStructureString({int indentation = 2}) {
 String result = "";
 String indentationStr = " " * indentation;
 if (true) {
 result += "{";
 this.forEach((key, value) {
 if (value is Map) {
 var temp = value.mapToStructureString(indentation: indentation + 2);
 result += "
$indentationStr" + ""$key" : $temp,";
 } else if (value is List) {
 result += "
$indentationStr" +
 ""$key" : ${value.listToStructureString(indentation: indentation + 2)},";
 } else {
 result += "
$indentationStr" + ""$key" : "$value",";
 }
 });
 result = result.substring(0, result.length - 1);
 result += indentation == 2 ? "
}" : "
${" " * (indentation - 1)}}";
 }
 return result;
 }
}
extension List2StringEx on List {
 String listToStructureString({int indentation = 2}) {
 String result = "";
 String indentationStr = " " * indentation;
 if (true) {
 result += "$indentationStr[";
 this.forEach((value) {
 if (value is Map) {
 var temp = value.mapToStructureString(indentation: indentation + 2);
 result += "
$indentationStr" + ""$temp",";
 } else if (value is List) {
 result += value.listToStructureString(indentation: indentation + 2);
 } else {
 result += "
$indentationStr" + ""$value",";
 }
 });
 result = result.substring(0, result.length - 1);
 result += "
$indentationStr]";
 }
 return result;
 }
}

サンプルリクエスト

dartのjsonパースにはjson_serializableを使うことが推奨されています!

void request() async {
 ResultData res = await Api.request("param");
 if (res.isSuccess) {
 //リセットを取得する.dataJsonの解析が可能で、これは一般的にエンティティ・クラスを構築するために使用される。
 TestBean bean = TestBean.fromMap(res.data);
 }else{
 //エラーを処理する
 }
 }
Demo

もしお役に立つと思われましたら、星を付けていただけると幸いです。)

Read next

Gradleソースコード読み込み関連

2.依存関係の下にある関連するaarを取得 3.

Feb 14, 2020 · 2 min read