Flutter中的单例以及网络请求库的封装
Posted sundaysme
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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实战》开源电子书 - 掘金
以上是关于Flutter中的单例以及网络请求库的封装的主要内容,如果未能解决你的问题,请参考以下文章
VSCode自定义代码片段14——Vue的axios网络请求封装