Flutter 实现电商 APP 之 网络请求的封装与数据解析(三)

1,028 阅读3分钟

网络请求框架选择用 Dio传送门,截止当前最新的版本是 dio:^4.0.4

本文仅是对 dio 框架进行一定的封装

1、引入

在 pubspec.yaml 文件中添加

dependencies:
  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  dio: ^4.0.4

2、配置

a、Android 网络权限配置及 Https 正式的校验

打开 app 目录下的 AndroidManifest.xml 文件,并在其中 application 添加 "networkSecurityConfig" 或者 “usesCleartextTraffic” 这两种方式都可以

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="top.superbig.sof.shop_online_flutter">
    <!-- 添加网络访问权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        android:icon="@mipmap/ic_launcher"
        android:label="Flutter 商城"
        android:networkSecurityConfig="@xml/network_security_config"
        android:usesCleartextTraffic="true"><!-- true 表示使用明文传输数据 -->
        <activity
            android:name=".MainActivity"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:windowSoftInputMode="adjustResize">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
                android:name="io.flutter.embedding.android.NormalTheme"
                android:resource="@style/NormalTheme" />
            <!-- Displays an Android View that continues showing the launch screen
                 Drawable until Flutter paints its first frame, then this splash
                 screen fades out. A splash screen is useful to avoid any visual
                 gap between the end of Android's launch screen and the painting of
                 Flutter's first frame. -->
            <meta-data
                android:name="io.flutter.embedding.android.SplashScreenDrawable"
                android:resource="@drawable/launch_background" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>

network_security_config.xml 在 res 目录下的 xml 目录中,没有 xml 则新建一个即可 network_security_config.xml 的内容如下

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <certificates src="system" /><!-- 表示信任所有,如果只需要其中部分请求可以明文传输可以使用一下-->
            <domain includeSubdomains="true">这里填写域名</domain>

        </trust-anchors>
    </base-config>
</network-security-config>

b、IOS 配置网络请求权限

设置图中属性值为 YES 即可 image.png

3、封装

代码如下

class HttpRequest{
  String _baseUrl;
  bool _needProxy = false;
  bool _needCert = false;
  ///dio 通用配置
  static BaseOptions optionParams = BaseOptions(
    connectTimeout: 15000,
    receiveTimeout: 10000,
    sendTimeout: 15000,

  );
  HttpRequest.create();

  ///设置baseUrl
  HttpRequest setBaseUrl(String baseUrl){
    _baseUrl = baseUrl;
    return this;
  }

  ///是否需要代理
  HttpRequest setNeedProxy(bool value) {
    _needProxy = value;
    return this;
  }

  ///是否需要校验证书
  HttpRequest setNeedCert(bool value){
    _needCert = value;
    return this;
  }

  String _getUrl(path){
    return _baseUrl==null?(Config.BASE_URL+path):(_baseUrl+path);
  }

  /// get 请求
  /// [path] 请求地址
  /// [params] 请求参数
  /// [header] 请求header
  Future<ResultDataEntity<T>> get<T>(String path,{Map<String,dynamic> params,Map header}) async{
    var url = _getUrl(path);
    return await request<T>(url, params, header, Options(method: "GET"));
  }

  /// post application/json 请求
  /// [path] 请求地址
  /// [params] 请求参数-拼接在Url 后
  /// [data] body 传参数
  /// [header] 请求header
  Future<ResultDataEntity<T>> postJson<T>(String path,{Map<String,dynamic> params,data,Map header}) async{
    var url = _getUrl(path);
    return await request<T>(url, params, header, Options(method: "POST",contentType: Headers.jsonContentType),data: data);
  }

  /// post application/x-www-form-urlencoded 请求
  /// [path] 请求地址
  /// [params] 请求参数-拼接在Url 后
  /// [data] body 传参数
  /// [header] 请求header
  Future<ResultDataEntity<T>> postUrl<T>(String path,{Map<String,dynamic> params,data,Map header}) async{
    var url = _getUrl(path);
    return await request<T>(url, params, header, Options(method: "POST",contentType: Headers.formUrlEncodedContentType),data: data);
  }

  /// post 表单请求
  /// [path] 请求地址
  /// [params] 请求参数-拼接在Url 后
  /// [data] body 传参数
  /// [header] 请求header
  Future<ResultDataEntity<T>> postForm<T>(String path,{Map<String,dynamic> params,data,Map header}) async{
    var url = _getUrl(path);
    var formData = FormData.fromMap(data);
    return await request<T>(url, params, header, Options(method: "POST"),data: formData);
  }

  Future<ResultDataEntity<T>> request<T>(String url,Map<String,dynamic> params,Map<String,String> header,Options options,{data}) async {
    var connectivityResult = await (new Connectivity().checkConnectivity());
    if(connectivityResult == ConnectivityResult.none){//判读网络链接状态
      return Future((){
        return  ResultDataEntity<T>.all(Code.SUCCESS,"网络未链接",null);
      });
    }
    if(!url.startsWith("https://") && !url.startsWith("http://")){//判读域名是否正确
      return Future((){
        return  ResultDataEntity<T>.all(Code.REQUEST_ERROR,"请求地址错误",null);
      });
    }

    //设置header
    Map<String,String> headers = new HashMap();
    if(header != null){
      headers.addAll(header);
    }

    if(options != null){
      options.headers = headers;
    }else{
      options = new Options(method: "GET");
      options.headers = headers;
    }
    //添加默认公共参数
    if(params != null){
      params.addAll(getCommonParams());
    }else{
      params = getCommonParams();
    }

    Dio dio = new Dio();
    dio.options = optionParams;//设置公共参数
    (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = getHttpClientProxy();//设置网络请求代理证书等
    //添加拦截器
    if(Config.DEBUG){//添加拦截器
      dio.interceptors.add(LogInterceptors());
    }

    Response response;
    try{
      response = await dio.request<String>(url,data:data,queryParameters: params,options: options);

    }on DioError catch(e){//错误方式处理
      switch(e.type){
        case DioErrorType.connectTimeout:
        case DioErrorType.sendTimeout:
        case DioErrorType.receiveTimeout:
          return Future((){return new ResultDataEntity.all(Code.NETWORK_TIMEOUT, "请求超时", null);});
        case DioErrorType.cancel:
          return Future((){return new  ResultDataEntity.all(Code.REQUEST_CANCEL, "请求取消", null);});
        case DioErrorType.response:
          return Future((){return new  ResultDataEntity.all(e.response.statusCode, e.message, null);});
        default:
          return Future((){return new  ResultDataEntity.all(Code.UNKNOWN, e.message, null);});
      }
    }on Error catch(e){
      return Future((){return new  ResultDataEntity.all(Code.UNKNOWN, e.toString(), null);});
    }
    try{
      if(response.statusCode == 200){//请求成功,解析数据
        String data = response.data;
        ResultDataEntity<T> result = ResultDataEntity();
        result.error_code = Code.SUCCESS;
        var jsonMap = json.decode(data);
        result.reason = "success";
        if(T == String){
          result.result = data as T;
        }else {
          result.result = JsonConvert.fromJsonAsT<T>(jsonMap);
        }
        return Future((){return result;});
      }
    }catch(e){
      return Future((){return ResultDataEntity.all(Code.SUCCESS, "success", response.data);});
    }
    return Future((){return new ResultDataEntity.all(Code.UNKNOWN, "未知错误", null);});
  }

  getHttpClientProxy(){
    return (HttpClient client){
      if(_needProxy) {
        client.findProxy = (uri) {
          return 'PROXY localhost:8888';//设置网络代理
        };
      }
      //配置 Https 证书校验
      client.badCertificateCallback = (X509Certificate cert,String host,int port){
        if(_needCert){
          if(cert.pem == Config.HTTPS_CERT_PEM){
            return true;
          }else{
            return false;
          }
        }else{
          return true;
        }
      };
    };
  }

  getToken() async{
    String token = await SpUtils.get(Config.TOKEN_KEY);
    return token;
  }

  static Map<String,dynamic> getCommonParams(){
    
    var data = {
      "app":1,
      "token":"176031ae-f6be-4d75-be67-6d85e1d0142f"
    };
    return data;
  }

}

基础数据的封装

class ResultDataEntity<T> {
 int error_code;
 String reason;
 T result;
 ResultDataEntity();
 ResultDataEntity.all(this.error_code,this.reason,this.result);
}

关于数据的解析,使用的是 FlutterJsonBeanFactory 插件,传入 json 字符串自动生成 bean 对象,包含 json 解析的代码

3、使用

var data = await HttpRequest.create().get<IconsEntity>(Api.APP_ICON,params: {"placeId":1});