Not long ago I saw the Dio package of Ivy Code, after groping, change it, use it well. Some fixes have been made for some of the invalid articles in the previous Avi Code boss article

Why do we have to wrap it up?

Token interception, error interception, same error handling, same cache, information encapsulation (false, true)

Cookies??? Roll came

Forget cookie, bye

Global initialization, passing in parameters

Dio initialization, passing baseUrl, connectTimeout, receiveTimeout, Options, Header interceptor, etc. Dio initialization allows us to pass in some configuration

Dio initialization configuration

Options, the latest version of Dio already uses requestOptions, merge, now uses copyWith. See below for details

If you want to waste a complete plan

You can refer to the flutter + GetX video app developed by using this solution. Those who have a star can enjoy some star.

Project address Github address apk download Panbaidu extract code: 3EV2

Initialize the

Interceptors can be passed in at initialization or by hand. For example, HERE I define four interceptors. The first one is used to add context-type: json to a global request. The second is the global error-handling interceptor, which is covered below. Cache interceptor, which handles interface cache data globally, retry interceptor (I haven’t used it yet)

    class Http {
      static final Http _instance = Http._internal();
      // The singleton uses the Http class,
      factory Http() => _instance;

      static late final Dio dio;
      CancelToken _cancelToken = new CancelToken();

      Http._internal() {
        // BaseOptions, Options, and RequestOptions can be configured with increasing priorities, and parameters can be overwritten based on priorities
        BaseOptions options = new BaseOptions();

        dio = Dio(options);

        // Add request interceptor
        dio.interceptors.add(RequestInterceptor());
        // Add an error interceptor
        dio.interceptors.add(ErrorInterceptor());
        // // Add cache interceptor
        dio.interceptors.add(NetCacheInterceptor());
        // // Add a Retry interceptor
        dio.interceptors.add(
          RetryOnConnectionChangeInterceptor(
            requestRetrier: DioConnectivityRequestRetrier(
              dio: dio,
              connectivity: Connectivity(),
            ),
          ),
        );

    // In debug mode, packet capture is required, so we use proxy and disable HTTPS certificate verification
    // if (PROXY_ENABLE) {
    // (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
    // (client) {
    // client.findProxy = (uri) {
    // return "PROXY $PROXY_IP:$PROXY_PORT";
    / /};
    The // // agent tool will provide a self-signed certificate for packet capture, which will fail certificate verification, so we disable certificate verification
    // client.badCertificateCallback =
    // (X509Certificate cert, String host, int port) => true;
    / /};
    // }
      }

      ///Initialize the public property
      ///
      /// [baseUrl] Indicates the address prefix
      /// [connectTimeout] Indicates the connection timeout time
      /// [receiveTimeout] Indicates the time limit for receiving a packet
      /// [interceptors] The base interceptor
      void init({
        String? baseUrl,
        int connectTimeout = 1500.int receiveTimeout = 1500.Map<String.String>? headers,
        List<Interceptor>? interceptors,
      }) {
        dio.options = dio.options.copyWith(
          baseUrl: baseUrl,
          connectTimeout: connectTimeout,
          receiveTimeout: receiveTimeout,
          headers: headers ?? const{});// Interceptors can be passed in when initializing HTTP classes
        if(interceptors ! =null && interceptors.isNotEmpty) {
          dio.interceptors..addAll(interceptors);
        }
      }

      / / close the dio
      void cancelRequests({required CancelToken token}) {
        _cancelToken.cancel("cancelled");
      }

      // Add authentication
      // Read the local configuration
      Map<String.dynamic>? getAuthorizationHeader() {
        Map<String.dynamic>? headers;
        // from getx or sputils
        // String accessToken = Global.accessToken;
        String accessToken = "";
        if(accessToken ! =null) {
          headers = {
            'Authorization': 'Bearer $accessToken'}; }return headers;
      }

      Future get(
        String path, {
        Map<String.dynamic>? params,
        Options? options,
        CancelToken? cancelToken,
        bool refresh = false.boolnoCache = ! CACHE_ENABLE,String? cacheKey,
        bool cacheDisk = false,})async {
        Options requestOptions = options ?? Options();
        requestOptions = requestOptions.copyWith(
          extra: {
            "refresh": refresh,
            "noCache": noCache,
            "cacheKey": cacheKey,
            "cacheDisk": cacheDisk,
          },
        );
        Map<String.dynamic>? _authorization = getAuthorizationHeader();
        if(_authorization ! =null) {
          requestOptions = requestOptions.copyWith(headers: _authorization);
        }
        Response response;
        response = await dio.get(
          path,
          queryParameters: params,
          options: requestOptions,
          cancelToken: cancelToken ?? _cancelToken,
        );

        return response.data;
      }

      Future post(
        String path, {
        Map<String.dynamic>? params,
        data,
        Options? options,
        CancelToken? cancelToken,
      }) async {
        Options requestOptions = options ?? Options();
        Map<String.dynamic>? _authorization = getAuthorizationHeader();
        if(_authorization ! =null) {
          requestOptions = requestOptions.copyWith(headers: _authorization);
        }
        var response = await dio.post(
          path,
          data: data,
          queryParameters: params,
          options: requestOptions,
          cancelToken: cancelToken ?? _cancelToken,
        );
        return response.data;
      }

      Future put(
        String path, {
        data,
        Map<String.dynamic>? params,
        Options? options,
        CancelToken? cancelToken,
      }) async {
        Options requestOptions = options ?? Options();

        Map<String.dynamic>? _authorization = getAuthorizationHeader();
        if(_authorization ! =null) {
          requestOptions = requestOptions.copyWith(headers: _authorization);
        }
        var response = await dio.put(
          path,
          data: data,
          queryParameters: params,
          options: requestOptions,
          cancelToken: cancelToken ?? _cancelToken,
        );
        return response.data;
      }

      Future patch(
        String path, {
        data,
        Map<String.dynamic>? params,
        Options? options,
        CancelToken? cancelToken,
      }) async {
        Options requestOptions = options ?? Options();
        Map<String.dynamic>? _authorization = getAuthorizationHeader();
        if(_authorization ! =null) {
          requestOptions = requestOptions.copyWith(headers: _authorization);
        }
        var response = await dio.patch(
          path,
          data: data,
          queryParameters: params,
          options: requestOptions,
          cancelToken: cancelToken ?? _cancelToken,
        );
        return response.data;
      }

      Future delete(
        String path, {
        data,
        Map<String.dynamic>? params,
        Options? options,
        CancelToken? cancelToken,
      }) async {
        Options requestOptions = options ?? Options();

        Map<String.dynamic>? _authorization = getAuthorizationHeader();
        if(_authorization ! =null) {
          requestOptions = requestOptions.copyWith(headers: _authorization);
        }
        var response = await dio.delete(
          path,
          data: data,
          queryParameters: params,
          options: requestOptions,
          cancelToken: cancelToken ?? _cancelToken,
        );
        returnresponse.data; }}Copy the code

Dio interceptor

Let’s take a look at interceptors. Here’s an example of handling interceptors

    // Here is an example of a soket error that I wrote separately, since dio does not allow message contents to be modified by default, I can only customize one to use
    class MyDioSocketException extends SocketException {
      late String message;

      MyDioSocketException(
        message, {
        osError,
        address,
        port,
      }) : super(
              message,
              osError: osError,
              address: address,
              port: port,
            );
    }

    /// Error handling interceptor
    class ErrorInterceptor extends Interceptor {
      // Whether there is a network
      Future<bool> isConnected() async {
        var connectivityResult = await (Connectivity().checkConnectivity());
        returnconnectivityResult ! = ConnectivityResult.none; }@override
      Future<void> onError(DioError err, ErrorInterceptorHandler errCb) async {
        // Create a custom socket instance, since dio-native instances, message is read-only
        // I added it separately, because the default dio Err instance lacks the error message when there is no network
        // Here I do the manual processing, to process the hand, the effect, see the picture below, you can see
        if (err.error isSocketException) { err.error = MyDioSocketException( err.message, osError: err.error? .osError, address: err.error? .address, port: err.error? .port, ); }// Dio default error instance, if there is no network, can only get an unknown error, can not accurately know whether there is no network
        if (err.type == DioErrorType.other) {
          bool isConnectNetWork = await isConnected();
          if(! isConnectNetWork && err.erroris MyDioSocketException) {
            err.error.message = "Current network unavailable, please check your network."; }}// Error unified processing
        AppException appException = AppException.create(err);
        // Error message
        debugPrint('DioError===: ${appException.toString()}');
        err.error = appException;
        return super.onError(err, errCb); }}Copy the code

As you can see from the above code, the ErrorInterceptor class inherits from the Interceptor class, and can re-state onRequest, onResponse, and onError. Finally, return super.onError passes the err instance to the superclass.

Unified error message packaging handling

Imagine if you had a project that had a dozen or so status codes, and each of them needed to convert the code into a text message, because sometimes you needed to give the user a hint. For example: connection timeout, request failure, network error, etc. The following is uniform error handling

AppException.dart

import 'package:dio/dio.dart';

/// Custom exception
class AppException implements Exception {
  final String _message;
  final int _code;

  AppException(
    this._code,
    this._message,
  );

  String toString() {
    return "$_code$_message";
  }

  String getMessage() {
    return _message;
  }

  factory AppException.create(DioError error) {
    switch (error.type) {
      case DioErrorType.cancel:
        {
          return BadRequestException(- 1."Request cancellation");
        }
      case DioErrorType.connectTimeout:
        {
          return BadRequestException(- 1."Connection timed out");
        }
      case DioErrorType.sendTimeout:
        {
          return BadRequestException(- 1."Request timed out");
        }
      case DioErrorType.receiveTimeout:
        {
          return BadRequestException(- 1."Response timeout");
        }
      case DioErrorType.response:
        {
          try {
            int?errCode = error.response! .statusCode;// String errMsg = error.response.statusMessage;
            // return ErrorEntity(code: errCode, message: errMsg);
            switch (errCode) {
              case 400:
                {
                  returnBadRequestException(errCode! ."Request syntax error");
                }
              case 401:
                {
                  returnUnauthorisedException(errCode! ."No access");
                }
              case 403:
                {
                  returnUnauthorisedException(errCode! ."Server refused to execute");
                }
              case 404:
                {
                  returnUnauthorisedException(errCode! ."Unable to connect to server");
                }
              case 405:
                {
                  returnUnauthorisedException(errCode! ."Request method prohibited");
                }
              case 500:
                {
                  returnUnauthorisedException(errCode! ."Server internal error");
                }
              case 502:
                {
                  returnUnauthorisedException(errCode! ."Invalid request");
                }
              case 503:
                {
                  returnUnauthorisedException(errCode! ."Server is down.");
                }
              case 505:
                {
                  returnUnauthorisedException(errCode! ."HTTP request not supported");
                }
              default:
                {
                  // return ErrorEntity(code: errCode, message: "unknown error ");
                  returnAppException(errCode! , error.response! .statusMessage!) ; }}}on Exception catch (_) {
            return AppException(- 1."Unknown error"); }}default:
        {
          return AppException(- 1, error.error.message); }}}}/// Request error
class BadRequestException extends AppException {
  BadRequestException(int code, String message) : super(code, message);
}

/// Unauthenticated exception
class UnauthorisedException extends AppException {
  UnauthorisedException(int code, String message) : super(code, message);
}

Copy the code

When you use it, use it like this,

Future<ApiResponse<Feed>> getFeedData(url) async {
    try {
      dynamic response = await HttpUtils.get(url);
      // print(response);
      Feed data = Feed.fromJson(response);
      return ApiResponse.completed(data);
    } on DioError catch (e) {
      print(e);
      // If there is an error request, use AppException to handle the error object
      // After processing, you can, for example, play a toast and prompt the user,
      // Popup toast is called in the following method
      return ApiResponse.error(e.error);
    }
  }
  
Future<void> _refresh() async {
    
    ApiResponse<Feed> swiperResponse = await getFeedData(initPageUrl);
    
    // After processing, we can get two states, status.pleted and status.error
    / / look here
    if (swiperResponse.status == Status.COMPLETED) {
        // Successful code, do whatever you want
    }else if (swiperResponse.status == Status.ERROR) {
        // Failed code can be given a toast to the user
        // For example, I'm prompting the user here
        / / use the exception! .getMessage(); Get the text message of the error object, is our interceptor after processing prompt text, non-English, get this, prompt to the user is not sweet?? Look at the image effect below
        String errMsg = swiperResponse.exception!.getMessage();
        publicToast(errMsg);
    }
}
Copy the code

The hint here is that the code added to the custom ERR interceptor cannot be supplemented for DIO whether there is no network

Disk cache data, interceptor

Disk cache interface data. First we encapsulate a SpUtil class, sputils. Dart

class SpUtil {
  SpUtil._internal();
  static final SpUtil _instance = SpUtil._internal();

  factory SpUtil() {
    return _instance;
  }

  SharedPreferences? prefs;

  Future<void> init() async {
    prefs = await SharedPreferences.getInstance();
  }

  Future<bool> setJSON(String key, dynamic jsonVal) {
    String jsonString = jsonEncode(jsonVal);
    returnprefs! .setString(key, jsonString); }dynamic getJSON(String key) {
    String?jsonString = prefs? .getString(key);return jsonString == null ? null : jsonDecode(jsonString);
  }

  Future<bool> setBool(String key, bool val) {
    returnprefs! .setBool(key, val); }bool? getBool(String key) {
    returnprefs! .getBool(key); } Future<bool> remove(String key) {
    return prefs!.remove(key);
  }
}

Copy the code

Cache interceptor

const int CACHE_MAXAGE = 86400000;
const int CACHE_MAXCOUNT = 1000;
const bool CACHE_ENABLE = false;

class CacheObject {
  CacheObject(this.response)
      : timeStamp = DateTime.now().millisecondsSinceEpoch;
  Response response;
  int timeStamp;

  @override
  bool operator ==(other) {
    return response.hashCode == other.hashCode;
  }

  @override
  int get hashCode => response.realUri.hashCode;
}

class NetCacheInterceptor extends Interceptor {
  // To ensure that the iterator order is consistent with the object insertion time order, we use LinkedHashMap
  var cache = LinkedHashMap<String, CacheObject>();

  @override
  void onRequest(
    RequestOptions options,
    RequestInterceptorHandler requestCb,
  ) async {
    if(! CACHE_ENABLE) {return super.onRequest(options, requestCb);
    }

    // refresh indicates whether the cache is refreshed
    bool refresh = options.extra["refresh"] = =true;

    // Whether to cache the disk
    bool cacheDisk = options.extra["cacheDisk"] = =true;

    // If refresh, delete related cache first
    if (refresh) {
      // Delete the memory cache with the same URI
      delete(options.uri.toString());

      // Delete the disk cache
      if (cacheDisk) {
        await SpUtil().remove(options.uri.toString());
      }

      return;
    }

    // get requests to enable caching
    if (options.extra["noCache"] != true &&
        options.method.toLowerCase() == 'get') {
      String key = options.extra["cacheKey"]???? options.uri.toString();// Strategy 1 memory cache first, 2 disk cache second

      // 1 Memory cache
      var ob = cache[key];
      if(ob ! =null) {
        // If the cache is not expired, the cache contents are returned
        if ((DateTime.now().millisecondsSinceEpoch - ob.timeStamp) / 1000 <
            CACHE_MAXAGE) {
          return;
        } else {
          // If it has expired, delete the cache and continue to request the servercache.remove(key); }}// 2 Disk cache
      if (cacheDisk) {
        var cacheData = SpUtil().getJSON(key);
        if(cacheData ! =null) {
          return; }}}return super.onRequest(options, requestCb);
  }

  @override
  void onResponse(
      Response response, ResponseInterceptorHandler responseCb) async {
    // If caching is enabled, the returned result is saved to the cache
    if (CACHE_ENABLE) {
      await _saveCache(response);
    }
    return super.onResponse(response, responseCb);
  }

  Future<void> _saveCache(Response object) async {
    RequestOptions options = object.requestOptions;

    // Only get requests are cached
    if (options.extra["noCache"] != true &&
        options.method.toLowerCase() == "get") {
      // Policy: write cache for both memory and disk

      / / the cache key
      String key = options.extra["cacheKey"]???? options.uri.toString();// Disk cache
      if (options.extra["cacheDisk"] = =true) {
        await SpUtil().setJSON(key, object.data);
      }

      // Memory cache
      // If the number of caches exceeds the maximum number, the earliest record is removed first
      if(cache.length == CACHE_MAXCOUNT) { cache.remove(cache[cache.keys.first]); } cache[key] = CacheObject(object); }}void delete(Stringkey) { cache.remove(key); }}Copy the code

Began to encapsulate

class HttpUtils {
  static void init({
    required String baseUrl,
    int connectTimeout = 1500.int receiveTimeout = 1500.List<Interceptor>? interceptors,
  }) {
    Http().init(
      baseUrl: baseUrl,
      connectTimeout: connectTimeout,
      receiveTimeout: receiveTimeout,
      interceptors: interceptors,
    );
  }

  static void cancelRequests({required CancelToken token}) {
    Http().cancelRequests(token: token);
  }

  static Future get(
    String path, {
    Map<String.dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
    bool refresh = false.boolnoCache = ! CACHE_ENABLE,String? cacheKey,
    bool cacheDisk = false,})async {
    return await Http().get(
      path,
      params: params,
      options: options,
      cancelToken: cancelToken,
      refresh: refresh,
      noCache: noCache,
      cacheKey: cacheKey,
    );
  }

  static Future post(
    String path, {
    data,
    Map<String.dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    return await Http().post(
      path,
      data: data,
      params: params,
      options: options,
      cancelToken: cancelToken,
    );
  }

  static Future put(
    String path, {
    data,
    Map<String.dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    return await Http().put(
      path,
      data: data,
      params: params,
      options: options,
      cancelToken: cancelToken,
    );
  }

  static Future patch(
    String path, {
    data,
    Map<String.dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    return await Http().patch(
      path,
      data: data,
      params: params,
      options: options,
      cancelToken: cancelToken,
    );
  }

  static Future delete(
    String path, {
    data,
    Map<String.dynamic>? params,
    Options? options,
    CancelToken? cancelToken,
  }) async {
    return awaitHttp().delete( path, data: data, params: params, options: options, cancelToken: cancelToken, ); }}Copy the code

Inject, initialize

The main. The dart. Here is my personal example

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // debugPaintSizeEnabled = true;
  await initStore();
  runApp(MyApp());
}

Future<void> initStore() async {
  // Initialize the local storage class
  await SpUtil().init();
  // Initialize the Request class
  HttpUtils.init(
    baseUrl: Api.baseUrl,
  );
  // Global getx global injection,
  await Get.putAsync(() => HistoryService().init());
  print("Global injection");
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      debugShowCheckedModeBanner: false, initialRoute: PageRoutes.INIT_ROUTER, getPages: PageRoutes.routes, ); }}Copy the code

Use encapsulated examples

// Define a function that returns the future apiResponse to get the status of status
Future<ApiResponse<Feed>> getFeedData(url) async {
    try {
      dynamic response = await HttpUtils.get(url);
      // print(response);
      Feed data = Feed.fromJson(response);
      return ApiResponse.completed(data);
    } on DioError catch (e) {
      print(e);
      return ApiResponse.error(e.error);
    }
  }

  Future<void> _refresh() async {
    
    ApiResponse<Feed> swiperResponse = await getFeedData(initPageUrl);
    if(! mounted) {return;
    }
    // Use status.pleted to determine success
    if(swiperResponse.status == Status.COMPLETED) { setState(() { nextPageUrl = swiperResponse.data! .nextPageUrl; _swiperList = []; _swiperList.addAll(swiperResponse.data! .issueList! [0]! .itemList!) ; _itemList = []; });// Pull new, list
      await _loading();
      // Use status.ERROR to check for failure
    } else if (swiperResponse.status == Status.ERROR) {
      setState(() {
        stateCode = 2;
      });
      // Error, we can call getMessage() to get the error message. Prompt to users (Friendly prompt after Chinese translation)
      StringerrMsg = swiperResponse.exception! .getMessage(); publicToast(errMsg);print("Error occurred at position home bottomBar1 swiper, URL:${initPageUrl}");
      print(swiperResponse.exception); }}Copy the code

If you want to waste a complete plan

You can refer to the flutter + GetX video app developed by using this solution. Those who have a star can enjoy some star.

Project address Github address apk download Panbaidu extract code: 3EV2