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<String, dynamic> 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<String, dynamic> 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();
  }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352