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

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


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

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

Flutter中的单例以及网络请求库的封装
程序员
25 人赞同了该文章

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.comFlutter中的单例以及网络请求库的封装8 篇文章,再学不会 Flutter 你来打我!?mp.weixin.qq.comFlutter中的单例以及网络请求库的封装Flutter基础视频免费教程 共25集完成?juejin.im《Flutter实战》开源电子书 - 掘金?juejin.im

相关推荐