Flutter学习笔记--网络请求Dio封装

几乎所有的APP都离不开网络请求,而在Flutter中国内的Dio就是一个强大的开源网络请求库,对比Android开发中的OkHttp库就能发现,Dio中已经将我们在开发过程中网络请求遇到的绝大部分情境都做了对应的处理。这样我们就十分的省时省力了。

在实际开发中,由于每个开发者的习惯和经历不尽相同,所以我们一般还会对一个成型框架进行二次封装。

import 'dart:convert';
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:fluttertoast/fluttertoast.dart';

class OkDio {
  static final String GET = 'get';
  static final String POST = 'post';
  static final int UNKNOWERROR = -1;
  static final int SERVERERROR = -2;
  static final String SERVERERRORMSG = '服务器错误,请联系管理员';
  static final int SUCESSSCODE = 200;

  Dio dio;

  static OkDio _instance;

  CancelToken token;

  static OkDio getInstance() {
    if (_instance == null) {
      _instance = new OkDio();
    }
    return _instance;
  }

  OkDio() {
    dio = Dio(BaseOptions(
      baseUrl: '',
      connectTimeout: 10 * 1000,
      receiveTimeout: 10 * 1000,
      responseType: ResponseType.json,
      contentType: ContentType.json,
      headers: {},
      extra: {},
    ));
  }

  initCancelToken() {
    token = new CancelToken();
  }

  cancelCancelToken() {
    token.cancel('cancelled');
  }

  initOkDio() {
    if (dio != null) {
      (dio.transformer as DefaultTransformer).jsonDecodeCallback = parseJson;
    }
  }

  get(String path, {FormData params, Function onResponse, Function onError}) {
    _request(GET, path, params, onResponse, onError);
  }

  post(String path, {FormData params, Function onResponse, Function onError}) {
    _request(POST, path, params, onResponse, onError);
  }

  _request(String httpType, String path, FormData param, Function onResponse,
      Function onError) async {
    Response response;

    try {
      _addInterceptor();

      if (httpType == GET) {
        if (param == null || param.isEmpty) {
          if (token != null) {
            response = await dio
                .get(path, cancelToken: token)
                .catchError(catchError());
          } else {
            response = await dio.get(path).catchError(catchError());
          }
        } else {
          if (token != null) {
            response = await dio
                .get(path, queryParameters: param, cancelToken: token)
                .catchError(catchError());
          } else {
            response = await dio
                .get(path, queryParameters: param)
                .catchError(catchError());
          }
        }
      } else if (httpType == POST) {
        if (param.isEmpty) {
          if (token != null) {
            response = await dio
                .post(path, cancelToken: token)
                .catchError(catchError());
          } else {
            response = await dio.post(path).catchError(catchError());
          }
        } else {
          if (token != null) {
            response = await dio
                .post(path, queryParameters: param, cancelToken: token)
                .catchError(catchError());
          } else {
            response = await dio
                .post(path, queryParameters: param)
                .catchError(catchError());
          }
        }
      }

      if (response == null) {
        _error(onError, SERVERERRORMSG, UNKNOWERROR);
        return;
      }

      if (response.statusCode != SUCESSSCODE) {
        _error(onError, response.data.toString(), response.statusCode);
        return;
      }

      if (onResponse != null) {
        onResponse(response.data);
      }
    } catch (e) {
      _error(onError, e.toString(), UNKNOWERROR);
    }
  }

  Function catchError() {
    return (e) {
      if (CancelToken.isCancel(e)) {
        print('cancal:' + e.toString());
      } else {
        print('other:' + e.toString());
      }
    };
  }

  _error(Function onError, String errorMessage, int errorCode) {
    Fluttertoast.showToast(
        msg: errorMessage,
        toastLength: Toast.LENGTH_SHORT,
        gravity: ToastGravity.CENTER);

    if (onError != null) {
      onError(errorMessage, errorCode);
    }
  }

  _addInterceptor() {
    Dio tokenDio = new Dio();
    tokenDio.options = dio.options;
    Map csrfToken = new Map();
    dio.interceptors.add(InterceptorsWrapper(onRequest: (Options options) {
      if (csrfToken == null) {
        dio.lock();
        return tokenDio.get("/token").then((d) {
          options.headers["csrfToken"] = csrfToken = d.data['data']['token'];
          return options;
        }).whenComplete(() => dio.unlock());
      } else {
        options.headers["csrfToken"] = csrfToken;
        return options;
      }
    }));
    dio.interceptors.add(LogInterceptor(responseBody: true));
  }

  // 必须是顶层函数
  _parseAndDecode(String response) {
    return jsonDecode(response);
  }

  parseJson(String text) async {
    return await compute(_parseAndDecode, text);
  }

}

这样我们就可以轻松实现网络请求功能了,比如我们需要请求一个天气信息的接口:

@override
  void getWeatherTestInfo(FormData param) {
    OkDio.getInstance().get('http://www.mocky.io/v2/5d15db720e00001730a11574',
        params: param, onResponse: (data) {
      try {
        TestWeatherBox weatherModel = TestWeatherBox.fromJson(data);
        if (weatherModel.status == Const.HTTPRESULT_SUCESS) {
          _service.setTestWeatherInfo(weatherModel.data);
        } else {
          _service.handlerError(weatherModel.code, weatherModel.msg);
        }
      } catch (e) {
        print(e);
      }
    }, onError: (errmsg, errorcode) {});
  }

其中的FormData类型的输入参数是在Service层中通过对参数对象的转换得到的

    GetWeatherParam param =
        new GetWeatherParam('SWVYdX0Aqc9G8VOIY', 'nantong', 'zh-Hans', 'c');
    _presenter.getWeatherTestInfo(ParamUtils.toParam(param.toJson(param)));
  static FormData toParam(Map param) {
    FormData formData = new FormData();

    param.forEach((String key, dynamic value) {
      formData.add(key, value);
    });
    return formData;
  }

参数对象GetWeatherParam的写法可以参开我之前的Flutter学习笔记--JSON与序列化,自动生成json_serializable的方式model类,这样我们就可以将传入参数作为对象处理,不用再担心FormData的键值对形式造成key值写错,进而调试半天的情况了,毕竟我们的目标是减少BUG出现的可能性。

而在onResponse中服务器传来的Json字符串我们需要解析成对象,对象中包含status、errorcode、errormsg和主要的业务对象,具体的做法在Flutter学习笔记--JSON与序列化,自动生成json_serializable的方式model类中也有写过,此间不在重复说了。

然后当status为成功时(具体数值与你的后台约定),我们向Service层返回业务数据,当出现status不是成功时,如执行需要登录信息的接口时,发现用户尚未登录时,我们需要对这种业务类的异常做出处理,我们调用Service层统一处理此类异常的handlerError方法,在此方法中我们可以再调用自定义业务异常处理类ExceptionHandler中的方法即可。

class BaseService {
  handlerError(int code, String msg) {
    ExceptionHandler.handlerException(code, msg);
  }
}
class ExceptionHandler {
  static handlerException(int code, String msg) {
    ToastUtils.showShort(msg);
    // TODO handler exception
  }
}

在实际运行中,我们希望在页面在生命周期的dispose阶段,取消这个画面所有的请求,所以在页面的基类中,我们加上两段代码

  @override
  void initState() {
    super.initState();
    OkDio.getInstance().initCancelToken();
  }
  @override
  void dispose() {
    OkDio.getInstance().cancelCancelToken();
    super.dispose();
  }

你可能感兴趣的:(Flutter学习笔记--网络请求Dio封装)