新闻中心

Flutter Dio 网络工具类封装

  • 时间:
  • 浏览:244
  • 来源:怪兽分发

前言: Flutter 的网络请求,可以使用 Dart 原生网络请求 HttpClient,但是这样来就很多功能需要我们重新做一次轮子。所以一般来说,可以找个知名的第三方开源框架做封装使用。比如 Dio...

Flutter 引入 Dio 库

dio 是一个强大的 Dart Http请求库,支持 Restful API、FormData、拦截器、请求取消、Cookie 管理、文件上传/下载、超时、自定义适配器等...

打开 pub.dev/packages/di… 找到最新版本。

添加依赖

dependencies:
  dio: ^3.x.x  // 请使用 pub 上 3.0.0 分支的最新版本
复制代码

极简的示例

import 'package:dio/dio.dart';
void getHttp() async {
  try {
    Response response = await Dio().get("http://www.baidu.com");
    print(response);
  } catch (e) {
    print(e);
  }
}
复制代码

关于 Dio 详细的使用说明,可以看说明文档: github.com/flutterchin…

Dio 封装

为什么要封装

  1. 可以做一些公共处理,方便灵活使用。
  2. 方便以后换成别的第三方开源框架。

DioManager

单例网络工具类

import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:smarthome/common/api.dart';
import 'package:smarthome/common/err_code.dart';
import 'package:smarthome/common/global.dart';
import 'package:smarthome/models/base.dart';
import 'package:smarthome/models/token.dart';
import 'package:smarthome/utils/sp_util.dart';
import 'package:smarthome/mlui/ml_toast.dart';
import 'package:smarthome/routes/routes.dart';

/// Dio 请求方法
enum DioMethod {
  get,
  post,
  put,
  delete,
}

/// 网络工具类
class DioManager {
  // 单例
  factory DioManager() =>_getInstance();
  static DioManager _instance;
  static DioManager _getInstance() {
    if (_instance == null) {
      _instance = DioManager._init();
    }
    return _instance;
  }

  Dio _dio;
  /// 初始化
  DioManager._init() {
    if (_dio == null) {
      // 设置 Dio 默认配置
      _dio = Dio(BaseOptions(
        // 请求基地址
        baseUrl: Api.baseUrl,
        // 连接服务器超时时间,单位是毫秒
        connectTimeout: 60*1000,
        // 接收数据的最长时限
        receiveTimeout: 60*1000,
        // Http请求头
        headers: {
          "api": "1.0.0",
        }
      ));

      // 请求与响应拦截器
      _dio.interceptors.add(OnReqResInterceptors());
      // 异常拦截器
      _dio.interceptors.add(OnErrorInterceptors());
    }
  }

  /// get请求
  Future get({@required String url, Map params}) async {
     return await requestHttp(url, method: DioMethod.get, params: params);
  }

  /// post 请求
  Future post({@required String url, Map params}) async {
    return await requestHttp(url, method: DioMethod.post, params: params);
  }

  /// put 请求
  Future put({@required String url, Map params}) async {
    return await requestHttp(url, method: DioMethod.put, params: params);
  }

  /// delete 请求
  Future delete({@required String url, Map params}) async {
    return await requestHttp(url, method: DioMethod.delete, params: params);
  }

  /// Dio request 方法
  Future requestHttp(String url, {DioMethod method = DioMethod.get, Map params}) async {
    const methodValues = {
      DioMethod.get: 'get',
      DioMethod.post: 'post',
      DioMethod.delete: 'delete',
      DioMethod.put: 'put'
    };

    // 添加 token
    TokenModel tokenModel = await SpUtil().loadToken();
    if (tokenModel.userToken != null) {
      _dio.options.headers = {'Authorization': 'Bearer ' + tokenModel.userToken};
    }

    try {
      Response response;
      // 不同请求方法,不同的请求参数。按实际项目需求分,这里 get 是 queryParameters,其它用 data. FormData 也是 data
      // 注意: 只有 post 方法支持发送 FormData.
      switch (method) {
        case DioMethod.get:
          response = await _dio.request(url, queryParameters: params, options: Options(method: methodValues[method]));
          break;
        default:
          response = await _dio.request(url, data: params, options: Options(method: methodValues[method]));
      }

      // JSON 序列化, Response<dynamic> 转 Map<String, dynamic>
      String jsonStr = json.encode(response.data);
      Map<String, dynamic> map = json.decode(jsonStr);

      // PS: 取得 json 数据后, 只返回需要的数据,如果数据没有在外面包一层 BaseModel, 直接返回就可以。
      var baseModel = BaseModel.fromJson(map);
      return baseModel.data;

    } on DioError catch (e) {
//      throw e;
//      print('DioError--- ${e.type}');
      // 出现错误都返回空,错误在 OnErrorInterceptors 类处理。
      return null;
    }
  }
}
复制代码

OnReqResInterceptors 请求与响应拦截器

/// Dio 请求与响应拦截器
class OnReqResInterceptors extends InterceptorsWrapper {
  /// 请求拦截
  @override
  Future onRequest(RequestOptions options) {
    if (Global.isDebug) {
      print("请求baseUrl:${options.baseUrl}");
      print("请求url:${options.path}");
      print('请求头: ${options.headers.toString()}');
      if (options.data != null) {
        print('请求参数: ${options.data.toString()}');
      }
    }
    return super.onRequest(options);
  }

  /// 响应拦截
  @override
  Future onResponse(Response response) {
    Response res = response;
    if (response != null) {
      if (Global.isDebug) {
        print('响应: ${response.toString()}');
      }
    }
    if (response.statusCode == 200) {
      int errCode = response.data["errCode"];
      if (errCode == ErrCode.SUCCESS) {
        res = response;
      }
    }
    return super.onResponse(res);
  }
}
复制代码

OnErrorInterceptors 拦截器

/// Dio OnError 拦截器
class OnErrorInterceptors extends InterceptorsWrapper {
  /// 异常拦截
  @override
  Future onError(DioError err) {
    if (Global.isDebug) {
      print('请求异常: ${err.toString()}');
      print('请求异常信息: ${err.response?.toString() ?? ""}');
    }
    // 异常分类
    switch (err.type) {
      // 4xx 5xx response
      case DioErrorType.RESPONSE:
      // JSON 序列化, Response<dynamic> 转 Map<String, dynamic>
        String jsonStr = json.encode(err.response.data);
        Map<String, dynamic> map = json.decode(jsonStr);
        BaseModel baseModel = BaseModel.fromJson(map);
        // 处理自定义错误
        switch (baseModel.errCode) {
          case ErrCode.SUCCESS:
            print('0 在这里是不可能出现的,出现的就是有错');
            break;
          case ErrCode.TOKEN_ERR:
            MLToast.error('未登陆');
            // 跳转到登录页
            Routes.toWelcome();
            break;
          case ErrCode.DEVICE_TIMEOUT:
            MLToast.error('设备响应超时');
            break;
          default:
            print('DioError default');
            // 错误提示
            MLToast.error(baseModel.message);
        }
        break;
      case DioErrorType.CONNECT_TIMEOUT:
        MLToast.error('连接超时');
        break;
      case DioErrorType.SEND_TIMEOUT:
        MLToast.error('发送超时');
        break;
      case DioErrorType.RECEIVE_TIMEOUT:
        MLToast.error('接收超时');
        break;
      case DioErrorType.CANCEL:
        MLToast.error('取消连接');
        break;
      case DioErrorType.DEFAULT:
        MLToast.error('连接异常');
        break;
    }
    return super.onError(err);
  }
}
复制代码

API 地址类

class Api {
  /// 基类
  static const baseUrl = "https://www.xxx.com/api/v1";
  /// 主页
  static const homeUrl = "/home";
}
复制代码

ErrCode 错误编码类

/// 错误编码
class ErrCode {

  /// 成功
  static const SUCCESS = 0;
  /// 权限错误
  static const TOKEN_ERR = 20401;
}
复制代码

SpUtil

另外,这里封装了 shared_preferences 工具类 来保存 Token, 这个按自己实际需求做就行,不用引入。

import 'package:flutter/cupertino.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:smarthome/models/token.dart';

/// shared_preferences 工具类-保存与读取数据
class SpUtil {
  /// Token
  static const exp = 'token_exp';
  static const id = 'token_id';
  static const name = 'token_name';
  static const haveGateway = 'token_haveGateway';
  static const userToken = 'token_user';
  static const deviceNumber = 'token_deviceNumber';

  /// 保存 Token
  saveToken({@required TokenModel tokenModel}) async {
      SharedPreferences sp = await SharedPreferences.getInstance();
      sp.setInt(exp, tokenModel.exp);
      sp.setInt(id, tokenModel.id);
      sp.setString(name, tokenModel.name);
      sp.setInt(haveGateway, tokenModel.haveGateway);
      sp.setString(userToken, tokenModel.userToken);
      sp.setInt(deviceNumber, tokenModel.deviceNumber);
  }

  /// 加载 Token
  Future<TokenModel> loadToken() async {
      SharedPreferences sp = await SharedPreferences.getInstance();
      TokenModel tokenModel = TokenModel();
      tokenModel.exp = sp.getInt(exp);
      tokenModel.id = sp.getInt(id);
      tokenModel.name = sp.getString(name);
      tokenModel.haveGateway = sp.getInt(haveGateway);
      tokenModel.userToken = sp.getString(userToken);
      tokenModel.deviceNumber = sp.getInt(deviceNumber);
      return tokenModel;
  }

  /// 移除 Token
  removeToken() async {
      SharedPreferences sp = await SharedPreferences.getInstance();
      sp.remove(exp);
      sp.remove(id);
      sp.remove(name);
      sp.remove(haveGateway);
      sp.remove(userToken);
      sp.remove(deviceNumber);
  }
}
复制代码

BaseModel 基类

class BaseModel {
  String message;
  int errCode;
  dynamic data;

  BaseModel({this.message, this.errCode, this.data});

  BaseModel.fromJson(Map<String, dynamic> json) {
    message = json['message'];
    errCode = json['errCode'];
    data = json['data'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['message'] = this.message;
    data['errCode'] = this.errCode;
    data['data'] = this.data;
    return data;
  }
}
复制代码

DioManager 使用方法

Get 请求

/// 获取所有场景
  static Future<List<SceneModel>> getScenes() async {
    var json = await DioManager().get(url: Api.scenesUrl);
    // 请求数据出错
    if (json == null) {
      return null;
    }
    List jsonArray = json;
    var sceneModels = List<SceneModel>();
    jsonArray.forEach((element) {
      sceneModels.add(SceneModel.fromJson(element));
    });

    return sceneModels;
  }
复制代码

Post 请求

  /// 保存场景
  static Future saveScene({@required Map params}) async {
    var json = await DioManager().post(url: Api.scenesUrl, params: params);
    // 请求数据出错
    if (json == null) {
      return null;
    }
    print(json);
    return '保存成功';
  }
复制代码

Put 请求

  /// 更新场景
  static Future updataScene({@required Map params}) async {
    var json = await DioManager().put(url: Api.scenesUrl, params: params);
    // 请求数据出错
    if (json == null) {
      return null;
    }
    print(json);
    return '更新成功';
  }
复制代码

Delete 请求

    /// 删除场景 
  static Future removeScene({@required int sceneId}) async {
    var params = {"sceneId": sceneId};
    var json = await DioManager().delete(url: Api.scenesUrl, params: params);
    // 请求数据出错
    if (json == null) {
      return null;
    }
    return '删除成功';
 }
复制代码

后记: 网络工具类基本封装完成,这里请求返回的数据类型都是 JSON 序列化, 然后转成基础模型,如果返回的数据是 String 类,就那么就用做转化就行。

作者:SquallHu

链接:https://juejin.im/post/6875185393810014221

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。