Flutter中的单例以及网络请求库的封装
https://zhuanlan.zhihu.com/p/53498914
Flutter中的单例以及网络请求库的封装
Why?为什么需要单例
在Android中我们经常使用OkHttp来进行网络请求,但我们并不希望每次都创建一个OkHttpClient;亦或有些资源初始化非常麻烦,消耗性能,我们希望一次创建,处处使用。这时候就需要单例。Dio作为flutter中的OkHttp,我们也可以用单例模式对其进行封装。
How?如何用dart实现单例
单例一般有这几个特征:
- 隐藏类的构造函数
- 提供一个方法获取该类的实例(Java中常常是一个静态方法,通过DCL或静态内部类等方法,返回一个实例)
- 该实例只能被创建一次,内存中独一份,任何地方通过调用特征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.com8 篇文章,再学不会 Flutter 你来打我!?mp.weixin.qq.comFlutter基础视频免费教程 共25集完成?juejin.im《Flutter实战》开源电子书 - 掘金?juejin.im