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的秘籍8 篇文章,再学不会 Flutter 你来打我!Flutter基础视频免费教程 共25集完成《Flutter实战》开源电子书 - 掘金
https://blog.csdn.net/aau88497/article/details/102344984
地址:https://pub.flutter-io.cn/packages/common_utils#-readme-tab-
Dart常用工具类库 common_utils
1、TimelineUtil : 时间轴.(新)
2、TimerUtil : 倒计时,定时任务.(新)
3、MoneyUtil : 精确转换,元转分,分转元,支持格式输出.(新)
4、LogUtil : 简单封装打印日志.(新)
5、DateUtil : 日期转换格式化输出.
6、RegexUtil : 正则验证手机号,身份证,邮箱等等.
7、NumUtil : 保留x位小数, 精确加、减、乘、除, 防止精度丢失.
8、ObjectUtil : 判断对象是否为空(String List Map),判断两个List是否相等.
Flutter工具类库 flustars
1、DioUtil : Dio 工具类.
2、SpUtil : 单例"同步" SharedPreferences 工具类.
3、ScreenUtil : 屏幕适配,获取屏幕宽、高、密度,AppBar高,状态栏高度,屏幕方向.
4、WidgetUtil : Widget渲染监听,获取Widget宽高,在屏幕上的坐标.
Add dependency #
-
dependencies:
-
common_utils: x.x.x #latest version
-
APIs #
-
TimelineUtil -> Example
-
///(xx)为可配置输出
-
enum DayFormat {
-
///(小于10s->刚刚)、x分钟、x小时、(昨天)、x天.
-
Simple,
-
///(小于10s->刚刚)、x分钟、x小时、[今年: (昨天/1天前)、(2天前)、MM-dd],[往年: yyyy-MM-dd].
-
Common,
-
///小于10s->刚刚)、x分钟、x小时、[今年: (昨天 HH:mm/1天前)、(2天前)、MM-dd HH:mm],[往年: yyyy-MM-dd HH:mm].
-
Full,
-
}
-
///Timeline信息配置.
-
abstract class TimelineInfo {
-
String suffixAgo(); //suffix ago(后缀 后).
-
String suffixAfter(); //suffix after(后缀 前).
-
String lessThanTenSecond() => ''; //just now(刚刚).
-
String customYesterday() => ''; //Yesterday(昨天).优先级高于keepOneDay
-
bool keepOneDay(); //保持1天,example: true -> 1天前, false -> MM-dd.
-
bool keepTwoDays(); //保持2天,example: true -> 2天前, false -> MM-dd.
-
String oneMinute(int minutes); //a minute(1分钟).
-
String minutes(int minutes); //x minutes(x分钟).
-
String anHour(int hours); //an hour(1小时).
-
String hours(int hours); //x hours(x小时).
-
String oneDay(int days); //a day(1天).
-
String days(int days); //x days(x天).
-
DayFormat dayFormat(); //format.
-
}
-
setLocaleInfo : 自定义设置配置信息.
-
formatByDateTime : 格式输出时间轴信息 by DateTime .
-
format : 格式输出时间轴信息.
-
-
TimerUtil -> Example
-
setInterval : 设置Timer间隔.
-
setTotalTime : 设置倒计时总时间.
-
startTimer() : 启动定时Timer.
-
startCountDown : 启动倒计时Timer.
-
updateTotalTime : 重设倒计时总时间.
-
cancel : 取消计时器.
-
setOnTimerTickCallback : 计时器回调.
-
isActive : Timer是否启动.
-
MoneyUtil 精确转换,防止精度丢失 -> Example
-
changeF2Y : 分 转 元, format格式输出.
-
changeFStr2YWithUnit : 分字符串 转 元, format 与 unit 格式 输出.
-
changeF2YWithUnit : 分 转 元, format 与 unit 格式 输出.
-
changeYWithUnit : 元, format 与 unit 格式 输出.
-
changeY2F : 元 转 分.
-
LogUtil #
-
init(isDebug, tag) : isDebug: 模式, tag 标签.
-
e(object, tag) : 日志e
-
v(object, tag) : 日志v,只在debug模式输出.
-
-
NumUtil -> Example
-
getIntByValueStr : 数字字符串转int.
-
getDoubleByValueStr : 数字字符串转double.
-
getNumByValueStr : 保留x位小数 by 数字字符串.
-
getNumByValueDouble : 保留x位小数 by double.
-
add : 加(精确相加,防止精度丢失).
-
subtract : 减(精确相减,防止精度丢失).
-
multiply : 乘(精确相乘,防止精度丢失).
-
divide : 除(精确相除,防止精度丢失).
-
remainder : 余.
-
lessThan : < .
-
thanOrEqual : <= .
-
greaterThan : > .
-
greaterOrEqual : >= .
-
-
DateUtil -> Example
-
enum DateFormat {
-
DEFAULT, //yyyy-MM-dd HH:mm:ss.SSS
-
NORMAL, //yyyy-MM-dd HH:mm:ss
-
YEAR_MONTH_DAY_HOUR_MINUTE, //yyyy-MM-dd HH:mm
-
YEAR_MONTH_DAY, //yyyy-MM-dd
-
YEAR_MONTH, //yyyy-MM
-
MONTH_DAY, //MM-dd
-
MONTH_DAY_HOUR_MINUTE, //MM-dd HH:mm
-
HOUR_MINUTE_SECOND, //HH:mm:ss
-
HOUR_MINUTE, //HH:mm
-
-
ZH_DEFAULT, //yyyy年MM月dd日 HH时mm分ss秒SSS毫秒
-
ZH_NORMAL, //yyyy年MM月dd日 HH时mm分ss秒 / timeSeparate: ":" --> yyyy年MM月dd日 HH:mm:ss
-
ZH_YEAR_MONTH_DAY_HOUR_MINUTE, //yyyy年MM月dd日 HH时mm分 / timeSeparate: ":" --> yyyy年MM月dd日 HH:mm
-
ZH_YEAR_MONTH_DAY, //yyyy年MM月dd日
-
ZH_YEAR_MONTH, //yyyy年MM月
-
ZH_MONTH_DAY, //MM月dd日
-
ZH_MONTH_DAY_HOUR_MINUTE, //MM月
-
dd日 HH时mm分 / timeSeparate: ":" --> MM月dd日 HH:mm
-
ZH_HOUR_MINUTE_SECOND, //HH时mm分ss秒
-
ZH_HOUR_MINUTE, //HH时mm分
-
}
-
getNowDateMs : 获取现在 毫秒.
-
getNowDateStr : 获取现在 日期字符串.(yyyy-MM-dd HH:mm:ss)
-
getDateMsByTimeStr : 获取毫秒 By 日期字符串(Format格式输出).
-
getDateStrByTimeStr : 获取日期字符串 By 日期字符串(Format格式输出).
-
getDateStrByMs : 获取日期字符串 By 毫秒(Format格式输出).
-
getDateStrByDateTime : 获取日期字符串 By DateTime(Format格式输出).
-
getWeekDay : 获取WeekDay By DateTime.
-
getZHWeekDay : 获取星期 By DateTime.
-
getWeekDayByMs : 获取WeekDay By 毫秒.
-
getZHWeekDayByMs : 获取星期 By 毫秒.
-
isLeapYearByYear : 是否是闰年.
-
yearIsEqual : 是否同年.
-
getDayOfYear : 在今年的第几天.
-
isYesterday : 是否是昨天.
-
isToday : 是否是今天.
-
-
RegexUtil -> Example
-
isMobileSimple : 简单验证手机号
-
isMobileExact : 精确验证手机号
-
isTel : 验证电话号码
-
isIDCard : 验证身份证号码
-
isIDCard15 : 验证身份证号码 15 位
-
isIDCard18 : 简单验证身份证号码 18 位
-
isIDCard18Exact : 精确验证身份证号码 18 位
-
isEmail : 验证邮箱
-
isURL : 验证 URL
-
isZh : 验证汉字
-
isDate : 验证 yyyy-MM-dd 格式的日期校验,已考虑平闰年
-
isIP : 验证 IP 地址
-
ObjectUtil -> Example
-
isEmptyString : 判断String是否为空.
-
isEmptyList : 判断List是否为空.
-
isEmptyMap : 判断Map是否为空.
-
isEmpty : 判断对象是否为空.(String List Map).
-
isNotEmpty : 判断对象是否非空.(String List Map).
-
twoListIsEqual : 判断两个List是否相等.
-
Example #
-
-
// Import package
-
import 'package:common_utils/common_utils.dart';
-
-
//TimelineUtil
-
DateTime xxxDateTime = DateTime(2018, 6, 16, 16, 16, 16);
-
LogUtil.e("Timeline: " + TimelineUtil.formatByDateTime(xxxDateTime, locale: 'zh').toString());
-
-
//MoneyUtil example
-
String moneyTxt = MoneyUtil.changeFStr2YWithUnit("1160", format: MoneyFormat.NORMAL, unit: MoneyUnit.YUAN_ZH);
-
String moneyTxt = MoneyUtil.changeYWithUnit("1.66", unit: MoneyUnit.YUAN_ZH);
-
-
//TimerUtil example
-
TimerUtil timerUtil;
-
//定时任务test
-
timerUtil = new TimerUtil(mInterval: 1000);
-
//timerUtil.setInterval(1000);
-
timerUtil.setOnTimerTickCallback((int value) {
-
LogUtil.e("TimerTick: " + value.toString());
-
});
-
timerUtil.startTimer();
-
//timerUtil.cancel();
-
-
TimerUtil timerCountDown;
-
//倒计时test
-
timerCountDown = new TimerUtil(mInterval: 1000, mTotalTime: 3 * 1000);
-
// timerCountDown.setInterval(1000);
-
// timerCountDown.setTotalTime(3 * 1000);
-
timerCountDown.setOnTimerTickCallback((int value) {
-
double tick = (value / 1000);
-
LogUtil.e("CountDown: " + tick.toInt().toString());
-
});
-
timerCountDown.startCountDown();
-
//timerUtil.cancel();
-
-
//LogUtil example
-
LogUtil.init(isDebug: true, tag: "test");
-
LogUtil.e("...log...", tag: "test");
-
LogUtil.v("...log...", tag: "test");
-
-
//DateUtil example
-
String timeNow = DateUtil.getDateStrByDateTime(DateTime.now());//2018-09-16 23:14:56
-
String timeNow = DateUtil.getDateStrByDateTime(DateTime.now(),format: DateFormat.ZH_NORMAL);//2018年09月16日 23时16分15秒
-
String weekday = DateUtil.getWeekDay(DateTime.parse("2018-09-16"));//Sunday
-
String weekdayZh = DateUtil.getZHWeekDay(DateTime.parse("2018-09-16"));//星期日
-
-
//First Page init. Notice!!!
-
ScreenUtil.getInstance().init(context);
-
-
ScreenUtil.screenWidth
-
ScreenUtil.screenHeight
-
ScreenUtil.statusBarHeight
-
ScreenUtil.screenDensity
-
-
List listA = ["A", "B", "C"];
-
List listB = ["A", "B", "C"];
-
print("Two List Is Equal: " + ObjectUtil.twoListIsEqual(listA, listB).toString());
-
-
// Global variable,Reference example
-
WidgetUtil widgetUtil = new WidgetUtil();
-
-
-
Widget build(BuildContext context) {
-
widgetUtil.asyncPrepare(context, false, (Rect rect) {
-
double width = rect.width;
-
double height = rect.height;
-
});
-
return ;
-
}
-
-
//Widgets must be rendered completely. Otherwise return Rect.zero.
-
Rect rect = WidgetUtil.getWidgetBounds(context);
-
double width = rect.width;
-
double height = rect.height;
-
-
//Widgets must be rendered completely. Otherwise return Offset.zero.
-
Offset offset = WidgetUtil.getWidgetLocalToGlobal(context);
-
double dx = offset.dx
-
double dx = offset.dy
-
转载于:https://www.cnblogs.com/ckAng/p/10538612.html