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