Flutter Dio的分层封装

23,187 阅读7分钟

背景

我们知道dio是一个非常强大的Dart Http请求库,支持非常多的功能,如果我们单单只是在外层包裹一层业务相关的封装,如果业务场景稍微复杂一点,这一层封装就显得臃肿和冗余了,当然我们的网络层本身也是为业务层服务,业务层有了需求,网络层自然得有响应了,只是响应的方式应该遵循低耦合、可用性、可维护性、可扩展性等原则。

设计

我在做iOS原生开发的时候深受Casa大神网络层设计的影响,这里将服务层(service)和接口层(api)划分开,这样做的好处有:

  1. 在面对不同服务时可单独配置相关的http强求配置,通常我们的应用在开发的过程中都不会只和一个服务交互,所以在这种场景下是非常有必要的。
  2. 服务层和接口层职责拆分,让他们做自己该做的事情。

设计.png

实现

服务层

服务层的职责包括:baseUrl的配置、服务公共请求头的配置、服务公共请求参数的配置、请求结果的加工、请求错误的统一处理等,除此之外还有token验证、log输出等。这里只提供一种封装思路具体的实现可以根据业务场景自行实现。 首先我们会创建一个抽象的基类,这里命名为Service,因为每一次请求都应该有一个全新的dio实例,Service只需要记录一个配置信息

import 'package:dio/dio.dart';

abstract class ServiceConfig {
  BaseOptions? options;
  
  void initOptions();
  
  Dio getDio() {
    Dio dio = Dio(options);
    initDio(dio);
      //可以继续实现一些基础设置 如添加全局的插件、代理等
      //dio.interceptors.add(),
      
    //if (httpProxyHost != null && httpProxyPort != null) {
    //  dioSetHttpProxy(dio, httpProxyHost!, httpProxyPort!);
    // }
    );
    return dio;
  }
}

void initDio(Dio dio) {}

管理service配置

初始化service后就需要保存ServiceConfig的实例。这里有两种方案,之前的版本是通过维护一个serviceKey来关联ServiceConfig吗,然后在程序启动时去注册一下。在使用的过程发现注册一下确实比较麻烦,所以在后面的版本直接是将ServiceConfig写成单例,效果是一样的,减少了serviceKey的维护。

添加serviceKey(老版本)

为了更好管理service,这里引入一个service来作为唯一标识。

import 'package:dio/dio.dart';

class Service {
  final Dio dio = Dio();
}

配置options

在使用dio时是需要配置options的,在初始化实现ServiceConfig时需要实现void initOptions();方法。可以在此方法下可以配置Service特有的属性,如下面的例子:

class MyService extends ServiceConfig {
  static final MyService _singleton = MyService._internal();

  factory MyService() {
    return _singleton;
  }

  MyService._internal() {
    initOptions();
  }
  
  @override
  void initOptions() {
    options = BaseOptions(
        baseUrl: "http://baseurl",
        connectTimeout: const Duration(milliseconds: 10000),
        receiveTimeout: const Duration(milliseconds: 80000),
        headers: {"Content-Type": 'application/json'},
        contentType: "application/json");
  }
 }

添加公共请求头、公共参数

通过子类实现serviceHeaderserviceQueryserviceBody三个方法来配置Service的配置。

import 'package:dio/dio.dart';

abstract class ServiceConfig {
  BaseOptions? options;
 
  Map<String, dynamic>? serviceHeader();

  Map<String, dynamic>? serviceQuery();

  Map<String, dynamic>? serviceBody();
 }

添加公共结果处理、错误处理

在请求结果到达后可对Api结果进行统一处理

import 'package:dio/dio.dart';

class ServiceConfig {
  BaseOptions? options;

  /*...省略其他设置...*/

 Map<String, dynamic> responseFactory(Map<String, dynamic> dataMap) {
     /*...可对请求结果进行加工...*/
    return dataMap;
  }
  
  String errorFactory(DioError error) {
    // 请求错误处理
    String errorMessage = error.message;
    /*...具体的错误处理...*/
    return errorMessage;
  }
}

Service的管理(老版本)

这里是引入了一个manager来管理多个Servicemanager为一个单例,用于注册和管理Service,实现也很简单,就使用一个serviceMap来存放Service,在使用服务前调用方法registeredService()注册需要服务即可,如果需要批量注册或者注销等功能自行添加即可。

import 'service.dart';

class ServiceManager {
  static final ServiceManager _instance = ServiceManager._internal();
  factory ServiceManager() {
    return _instance;
  }

  ServiceManager._internal() {
    init();
  }

  Map serviceMap = {};

  void init() {}

  void registeredService(Service service) {
    service.initDio();
    String key = service.serviceKey();
    serviceMap[key] = service;
  }
}

Api层

Api层的职责包括设置path、请求参数的组装、请求method配置等功能了。

定义一个自定义的method

enum RequestMethod { get, post, put, delete, patch, copy }

serviceKey的配置(老版本)

设置serviceKey是为了指定该Api属于哪一个服务的:

class BaseApi {
  String serviceKey() {
    return "";
  }
}

加上path和method配置

abstract class BaseApi {
  String path();

  RequestMethod method();

  ServiceConfig getService();
 }

核心request方法

此处只实现了部分method,如有需求自行实现即可:

class BaseApi {

  /*...省略其他部分...*/

  void request(
      {Map<String, dynamic>? query,
      Map<String, dynamic>? body,
      Map<String, dynamic>? header,
      required Function successCallBack,
      required Function errorCallBack}) async {
    //获取到对应的服务
    Service service;
    if (ServiceManager().serviceMap.containsKey(serviceKey())) {
      service = ServiceManager().serviceMap[serviceKey()];
    } else {
      throw Exception('服务尚未注册');
    }
    Dio dio = service.dio;

    Response? response;
    Map<String, dynamic>? queryParams = {};
    var globalQueryParams = service.serviceQuery();
    if (globalQueryParams != null) {
      queryParams.addAll(globalQueryParams);
    }
    if (query != null) {
      queryParams.addAll(query);
    }

    Map<String, dynamic>? headerParams = {};

    var globalHeaderParams = service.serviceHeader();
    if (globalHeaderParams != null) {
      headerParams.addAll(globalHeaderParams);
    }
    if (header != null) {
      headerParams.addAll(header);
    }

    Map<String, dynamic>? bodyParams = {};
    var globalBodyParams = service.serviceBody();
    if (globalBodyParams != null) {
      bodyParams.addAll(globalBodyParams);
    }
    if (body != null) {
      bodyParams.addAll(body);
    }

    String url = path();

    Options options = Options(headers: headerParams);

    try {
      switch (method()) {
        case RequestMethod.get:
          if (queryParams.isNotEmpty) {
            response = await dio.get(url,
                queryParameters: queryParams, options: options);
          } else {
            response = await dio.get(url, options: options);
          }
          break;
        case RequestMethod.post:
          if (body != null && body.isNotEmpty) {
            response = await dio.post(url, data: body, options: options);
          } else {
            response = await dio.post(url, options: options);
          }
          break;
        default:
      }
    } on DioError catch (error) {
      errorCallBack(service.errorFactory(error));
    }
    if (response != null && response.data != null) {
      String dataStr = json.encode(response.data);
      Map<String, dynamic> dataMap = json.decode(dataStr);
      dataMap = service.responseFactory(dataMap);
      successCallBack(dataMap);
    }
  }
}

使用

实现服务层

继承于ServiceConfig实现单例并完成相应的父类方法重写即可。

import 'package:dio/dio.dart';

import 'package:rm_http_request/request/service_config.dart';

class MyService extends ServiceConfig {
  static final MyService _singleton = MyService._internal();

  factory MyService() {
    return _singleton;
  }

  MyService._internal() {
    initOptions();
  }

  @override
  Map<String, dynamic>? serviceBody() {
    return null;
  }

  @override
  Map<String, dynamic>? serviceHeader() {
    Map<String, dynamic> header = <String, dynamic>{};
    header["_langtype"] = "zh_CN";
    return header;
  }

  @override
  Map<String, dynamic>? serviceQuery() {
    return null;
  }

  @override
  void initOptions() {
    options = BaseOptions(
        baseUrl: "http://baseurl",
        connectTimeout: const Duration(milliseconds: 10000),
        receiveTimeout: const Duration(milliseconds: 80000),
        headers: {"Content-Type": 'application/json'},
        contentType: "application/json");
  }

  @override
  String errorFactory(DioError error) {
    // 请求错误处理
    return "";
  }

  @override
  Map<String, dynamic> responseFactory(
      Map<String, dynamic> dataMap, api, response) {
    return dataMap;
  }
}


注册(老版本)

在网络请求前需要完成服务的注册:

void main() {
  runApp(MyApp());
  //注册服务
  ServiceManager().registeredService(CustomService());
}

Api的实现

Api的实现也是继承于BaseApi,然后实现相关的父类方法重写即可:

class LoginApi extends BaseApi {
  @override
  RequestMethod method() => RequestMethod.post;

  @override
  String path() => "/user/login";

  @override
  ServiceConfig getService() => MyService();

  void login(SuccessCallback successCallback, ErrorCallBack errorCallBack,
      {required String account, required String password}) {
    request(successCallback, errorCallBack,
        body: {'account': account, 'password': password});
  }
}

Api的调用

对上面的Api发起请求例子:

void requestApi() {
     LoginApi api = LoginApi();

    api.login((json) {
      print(json);
    }, (error, code) {
      print(error);
    }, account: '1', password: 'w');

    api.request(
      body: {"account": "1", "password": 2},
      (json) {
        print(json);
      },
      (error, code) {
        print(error);
      },
    );
}

ServiceConfig全部实现:

可根据需求自己去修改。

import 'dart:developer';

import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:rm_http_request/request/base_api.dart';

abstract class ServiceConfig {
  BaseOptions? options;
  String? httpProxyHost;
  String? httpProxyPort;

  Map<String, dynamic>? serviceHeader();

  Map<String, dynamic>? serviceQuery();

  Map<String, dynamic>? serviceBody();

  ///获取到业务数据后的 数据加工
  Map<String, dynamic> responseFactory(
      Map<String, dynamic> dataMap, BaseApi api, Response response);

  void initOptions();

  Dio getDio() {
    Dio dio = Dio(options);
    initDio(dio);
    if (httpProxyHost != null && httpProxyPort != null) {
      dioSetHttpProxy(dio, httpProxyHost!, httpProxyPort!);
    }
    // dio.interceptors.add(
    //   LoggyDioInterceptor(
    //     requestHeader: true,
    //     requestBody: true,
    //     requestLevel: LogLevel.debug,
    //     responseHeader: true,
    //     responseBody: true,
    //     responseLevel: LogLevel.info)
    //     );
    dio.interceptors.add(
      LogInterceptor(
        request: true,
        requestBody: true,
        requestHeader: true,
        responseBody: true,
        responseHeader: true,
        logPrint: (object) {
          log(object.toString());
        },
      ),
    );
    return dio;
  }

  void dioSetHttpProxy(Dio dio, String host, String port) {
    (dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate =
        (client) {
      client.findProxy = (uri) {
        //proxy all request to localhost:8888
        return "PROXY $host:$port"; //这里将localhost设置为自己电脑的IP,其他不变,注意上线的时候一定记得把代理去掉
      };
      return null;
    };
  }

  void initDio(Dio dio) {}

  String errorFactory(DioError error) {
    // 请求错误处理
    String errorMessage = error.response?.statusMessage ?? "";
    switch (error.type) {
      case DioErrorType.connectionTimeout:
        errorMessage = "Connection timeout";
        break;
      case DioErrorType.receiveTimeout:
        errorMessage = "Receive timeout";
        break;
      case DioErrorType.sendTimeout:
        errorMessage = "Send timeout";
        break;
      case DioErrorType.badResponse:
        try {
          int? errCode = error.response?.statusCode;
          switch (errCode) {
            case 400:
              errorMessage = "语法错误";
          }
        } on Exception catch (_) {
          errorMessage = error.response?.statusMessage ?? "";
        }
        break;
      // case DioErrorType.:
      //   errorMessage = error.response?.statusMessage ??"";
      //   break;
      default:
        errorMessage = error.response?.statusMessage ?? "";
    }
    return errorMessage;
  }
}


BaseApi实现

import 'dart:convert';

import 'service_config.dart';
import 'package:dio/dio.dart';

enum RequestMethod {
  get('GET'),
  post('POST'),
  put('PUT'),
  delete('DELETE'),
  patch('PATCH'),
  copy('COPY');

  final String value;
  const RequestMethod(this.value);
}

typedef SuccessCallback = void Function(Map<String, dynamic> json);
typedef ErrorCallBack = void Function(String error, int code);

abstract class BaseApi {
  String path();

  RequestMethod method();

  ServiceConfig getService();

  void request(SuccessCallback successCallBack, ErrorCallBack errorCallBack,
      {Map<String, dynamic>? query,
      Map<String, dynamic>? body,
      Map<String, dynamic>? header}) async {
    Response? response;
    ServiceConfig service = getService();
    try {
      response = await _baseRequest(query: query, body: body, header: header);
    } on DioError catch (error) {
      errorCallBack(service.errorFactory(error), 0);
    }
    if (response != null && response.data != null) {
      Map<String, dynamic> dataMap = _responseFactory(service, response);
      successCallBack(dataMap);
    }
  }

  Future<Map<String, dynamic>> requestAsync(
      {Map<String, dynamic>? query,
      Map<String, dynamic>? body,
      Map<String, dynamic>? header}) async {
    Response? response;
    ServiceConfig service = getService();
    try {
      response = await _baseRequest(query: query, body: body, header: header);
    } on DioError catch (error) {
      return {"success": false, "message": service.errorFactory(error)};
    }
    if (response.data != null) {
      Map<String, dynamic> dataMap = _responseFactory(service, response);
      return dataMap;
    }
    return {"success": false, "message": "请求失败"};
  }

  Future<Response<T>> _baseRequest<T>(
      {Map<String, dynamic>? query,
      Map<String, dynamic>? body,
      Map<String, dynamic>? header}) async {
    ServiceConfig service = getService();

    Dio dio = service.getDio();

    Map<String, dynamic> queryParams = _getQueryParams(service, query);

    Map<String, dynamic> headerParams = _getHeaderParams(service, header);

    Map<String, dynamic>? bodyParams = _getBodyParams(service, body);

    String url = path();

    Options options = Options(headers: headerParams, method: method().value);
    return dio.request(url,
        data: bodyParams, queryParameters: queryParams, options: options);
  }

  dynamic _responseFactory(ServiceConfig service, Response response) {
    String dataStr = json.encode(response.data);
    Map<String, dynamic> dataMap = json.decode(dataStr);
    dataMap = service.responseFactory(dataMap, this, response);
    return dataMap;
  }

  Map<String, dynamic> _getHeaderParams(
      ServiceConfig service, Map<String, dynamic>? header) {
    Map<String, dynamic> headerParams = {};
    var globalHeaderParams = service.serviceHeader();
    if (globalHeaderParams != null) {
      headerParams.addAll(globalHeaderParams);
    }
    if (header != null) {
      headerParams.addAll(header);
    }
    return headerParams;
  }

  Map<String, dynamic> _getQueryParams(
      ServiceConfig service, Map<String, dynamic>? query) {
    Map<String, dynamic>? queryParams = {};
    var globalQueryParams = service.serviceQuery();
    if (globalQueryParams != null) {
      queryParams.addAll(globalQueryParams);
    }
    if (query != null) {
      queryParams.addAll(query);
    }
    return queryParams;
  }

  Map<String, dynamic> _getBodyParams(
      ServiceConfig service, Map<String, dynamic>? body) {
    Map<String, dynamic>? bodyParams = {};
    var globalBodyParams = service.serviceBody();
    if (globalBodyParams != null) {
      bodyParams.addAll(globalBodyParams);
    }
    if (body != null) {
      bodyParams.addAll(body);
    }
    return bodyParams;
  }
}

demo(demo未更新)