Flutter中的单例以及网络请求库的封装

Posted sundaysme

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter中的单例以及网络请求库的封装相关的知识,希望对你有一定的参考价值。

https://zhuanlan.zhihu.com/p/53498914


技术图片

 

 

Flutter中的单例以及网络请求库的封装

 

Why?为什么需要单例

android中我们经常使用OkHttp来进行网络请求,但我们并不希望每次都创建一个OkHttpClient;亦或有些资源初始化非常麻烦,消耗性能,我们希望一次创建,处处使用。这时候就需要单例。Dio作为flutter中的OkHttp,我们也可以用单例模式对其进行封装。

How?如何用dart实现单例

单例一般有这几个特征:

  1. 隐藏类的构造函数
  2. 提供一个方法获取该类的实例(Java中常常是一个静态方法,通过DCL或静态内部类等方法,返回一个实例)
  3. 该实例只能被创建一次,内存中独一份,任何地方通过调用特征2中所述方法获取到的实例都应该是同一个

来看看《Dart Cookbook》中时如何实现单例模式的:

/*The singleton example shows how to do this 
(substitute your singleton class name for Immortal).
Use a factory constructor to implement the
 singleton pattern, as shown in the following code:*/
 class Immortal {
     static final Immortal theOne = new    Immortal._internal(Connor
   MacLeod);
     String name;
     factory Immortal(name) => theOne;
     // private, named constructor
     Immortal._internal(this.name);
}
   main() {
     var im1 = new Immortal(Juan Ramirez);
     var im2 = new Immortal(The Kurgan);
       print(im1.name);
     print(im2.name);
     print(Immortal.theOne.name);
     assert(identical(im1, im2));
}

可以看到,他通过私有的具名构造方法_internal()隐藏了构造方法,提供了一个工厂方法来获取该类的实例,并且用static final修饰了theOne,theOne会在编译期被初始化,保证了特征3。至于theOne为什么会在编译期初始化,参考 Static variable initialization opened up to any expression

Singleton In Action!在项目中使用单例

Dio是flutterchina提供的一个网络请求库,可以说是Flutter中的OkHttp,支持拦截器,缓存等特性。具体使用参考Dio github。我在项目中使用单例模式对Dio库进行了一层简单封装:

import "package:dio/dio.dart";
import ‘dart:async‘;

class HttpUtil {
  static final HttpUtil _instance = HttpUtil._internal();
  Dio _client;

  factory HttpUtil() => _instance;

  HttpUtil._internal() {
    if (null == _client) {
      Options options = new Options();
      options.baseUrl = "http://www.wanandroid.com";
      options.receiveTimeout = 1000 * 10; //10秒
      options.connectTimeout = 5000; //5秒
      _client = new Dio(options);
    }
  }

  Future<Map<String, dynamic>> get(String path,
      [Map<String, dynamic> params]) async {
    Response<Map<String, dynamic>> response;
    if (null != params) {
      response = await _client.get(path, data: params);
    } else {
      response = await _client.get(path);
    }
    return response.data;
  }
  //...省略post等方法...
}

One More Thing

App后端接口返回的数据格式一般都有固定结构,以wanandroid.com的开发Api为例:

{
  "data": {
    "curPage": 1,
    "datas": [],
    "offset": 0,
    "over": true,
    "pageCount": 0,
    "size": 20,
    "total": 0
  },
  "errorCode": 0,
  "errorMsg": ""
}

要解析这样的json,Android中可以玩出花。但是flutter禁用反射,也就没有类似Java中Gson这样的库。网上提供了flutter中,几种根据json自动生成model的方式,如下:

由于项目中的数据,结构固定,我采用范型+在线工具的的方式来实现我项目中json的解析,这种方法看起来有些笨拙(希望有同学可以提供更优雅的方式让我学习下):

1.定义一个抽象类,约定datas字段中model的行为:

abstract class JsonData{
  JsonData fromJson(Map<String, dynamic> json,JsonData mySelf);
}

2.抽象出data字段对应的model:

import package:flutter_app/bean/JsonData.dart;
import package:meta/meta.dart;
class PageData<T extends JsonData>{
  List<T> datas;
  int curPage;
  int pageCount;
  //...省略size,total等字段
  PageData.fromJson({@required Map<String, dynamic> json,@required JsonDataCreator beanCreator}) {
    // TODO: implement fromJson
    curPage = json[curPage];
    pageCount = json[pageCount];
    print(json);
    if (json[datas] != null) {
      datas = new List<T>();
      json[datas].forEach((v) {
        JsonData item = beanCreator();
        item.fromJson(v,item);
        datas.add(item);
      });
    }
  }
}
typedef JsonDataCreator = JsonData Function();

3.抽象出整个返回结果对应的model:

import package:flutter_app/bean/PageData.dart;
import package:flutter_app/bean/JsonData.dart;
import package:meta/meta.dart;
class BaseResult<T extends JsonData>{
  int errorCode;
  String errorMsg;
  PageData<T> data;

  BaseResult.fromJson({@required Map<String, dynamic> json,@required JsonDataCreator beanCreator}) {
    // TODO: implement fromJson
    errorCode = json[errorCode];
    errorMsg = json[errorMsg];
    data = PageData.fromJson(json:json[data],beanCreator: beanCreator);
  }
}

4.在项目中使用时,拿到json,放到在线工具中生成model,copy一下,改成以上约定的格式。以wanandroid文章列表接口为例,其返回结果如下:

{
  "data": {
    "curPage": 2,
    "datas": [
      {
        "apkLink": "",
        "author": "鸿洋",
        "chapterId": 408,
        "chapterName": "鸿洋",
        "collect": false,
        "courseId": 13,
        "desc": "",
        "envelopePic": "",
        ...其他字段
        "title": "一篇文本跳动控件,为你打开一扇大门,学会这两点心得,控件你也会写",
        "type": 0,
        "userId": -1,
        "visible": 1,
        "zan": 0
      },
      ....
     ],
     "errorCode":0,
     "errorMsg":
  }
 }

封装一个model对应datas中的一个数据:

import package:flutter_app/bean/JsonData.dart;
class ProjectBean extends JsonData{
  String title;
  String envelopePic;
  @override
  JsonData fromJson(Map<String, dynamic> json, JsonData mySelf) {
    // TODO: implement fromJson
    if(mySelf is ProjectBean){
      mySelf.title = json[title];
      mySelf.envelopePic = json[envelopePic];
      //...省略其他字段
    }
    return mySelf;
  }
} 

请求数据,并解析:

class ApiService{
  static Future<List<ProjectBean>> getProjectList() async{
    String url = "/project/list/1/json";
    Map<String,dynamic> response = await HttpUtil().get(url);
    BaseResult result = BaseResult<ProjectBean>.fromJson(json: response,beanCreator: ()=>ProjectBean());
    print(result.data.datas.length);
    return result.data.datas;
  }
}  

因为flutter中不能使用反射,我不知道有什么方法能够获得范型的实际类型,所以PageData的构造方法fromJson()需要传入一个闭包,用来创建model。

Thanks

感谢这些分享知识的作者,让我学习的过程中有资料可参考:

帮你整理一份快速入门Flutter的秘籍?mp.weixin.qq.com技术图片8 篇文章,再学不会 Flutter 你来打我!?mp.weixin.qq.com技术图片Flutter基础视频免费教程 共25集完成?juejin.im《Flutter实战》开源电子书 - 掘金?juejin.im

 

 

以上是关于Flutter中的单例以及网络请求库的封装的主要内容,如果未能解决你的问题,请参考以下文章

片段作为 Android 中的单例

Flutter学习-网络请求

基于dio库封装flutter项目的标准网络框架

VSCode自定义代码片段14——Vue的axios网络请求封装

VSCode自定义代码片段14——Vue的axios网络请求封装

VSCode自定义代码片段14——Vue的axios网络请求封装