Flutter网络和数据存储框架搭建
概述
无论是Flutter开发还是react-native开发都离不了和服务端进行数据交互。
数据的交互避免不了的需要进行网络请求,在大部分场景中数据交互是通过http请求完成的.对于繁杂的业务网络层所面临的挑,需要通过网络层架构设计来应对这些挑战。
对于繁杂的业务网络层所面临的挑战
- 切换成本高:网络操作使用的三方库存在不维护切换成本高的风险;
- 接口管理不便:对于大中型APP接口众多,不方便管理;
- 重复代码多:APP中进行数据交互的场景很多,网络请求存在大量的重复代码;
- 扩展性差:网络操作和业务代码耦合严重,不利于扩展;
- 开发效率低:不同三方库使用方式不统一,步骤繁琐开发效率低;
HitNet架构设计
设计要求:
- 支持网络库插拔设计,且不干扰业务层
- 简洁易用,支持配置来进行请求
- Adapter设计,扩展性强
- 统一异常和返回处理
目录结构
封装基础请求
// http/request/base_request.dart
enum HttpMethod { GET, POST, DELETE }
///基础请求
abstract class BaseRequest {
// curl -X GET "http://api.devio.org/uapi/test/test?requestPrams=11" -H "accept: */*"
// curl -X GET "https://api.devio.org/uapi/test/test/1
var pathParams;
var useHttps = true;
String authority() {
return "api.devio.org";
}
HttpMethod httpMethod();
String path();
String url() {
Uri uri;
var pathStr = path();
//拼接path参数
if (pathParams != null) {
if (path().endsWith("/")) {
pathStr = "${path()}$pathParams";
} else {
pathStr = "${path()}/$pathParams";
}
}
//http和https切换
if (useHttps) {
uri = Uri.https(authority(), pathStr, params);
} else {
uri = Uri.http(authority(), pathStr, params);
}
print('url:${uri.toString()}');
return uri.toString();
}
bool needLogin();
Map<String, String> params = Map();
///添加参数
BaseRequest add(String k, Object v) {
params[k] = v.toString();
return this;
}
Map<String, dynamic> header = {};
///添加header
BaseRequest addHeader(String k, Object v) {
header[k] = v.toString();
return this;
}
}
统一异常和返回处理
// http/core/hi_error.dart
///需要登录的异常
class NeedLogin extends HiNetError {
NeedLogin({int code = 401, String message = '请先登录'}) : super(code, message);
}
///需要授权的异常
class NeedAuth extends HiNetError {
NeedAuth(String message, {int code = 403, dynamic data})
: super(code, message, data: data);
}
///网络异常统一格式类
class HiNetError implements Exception {
final int code;
final String message;
final dynamic data;
HiNetError(this.code, this.message, {this.data});
}
封装请求抽象类
// http/core/hi_net_adapter.dart
import 'dart:convert';
import 'package:flutter_bili_app/http/request/base_request.dart';
///网络请求抽象类
abstract class HiNetAdapter {
Future<HiNetResponse<T>> send<T>(BaseRequest request);
}
/// 统一网络层返回格式
class HiNetResponse<T> {
HiNetResponse({
this.data,
required this.request,
this.statusCode,
this.statusMessage,
this.extra,
});
/// Response body. may have been transformed, please refer to [ResponseType].
T? data;
/// The corresponding request info.
BaseRequest request;
/// Http status code.
int? statusCode;
/// Returns the reason phrase associated with the status code.
/// The reason phrase must be set before the body is written
/// to. Setting the reason phrase after writing to the body.
String? statusMessage;
/// Custom field that you can retrieve it later in `then`.
late dynamic extra;
/// We are more concerned about `data` field.
@override
String toString() {
if (data is Map) {
return json.encode(data);
}
return data.toString();
}
}
安装并使用dio
插件地址: pub.dev/packages/di…
修改pubspec.yaml
dependencies:
dio: ^5.1.2
然后 flutter pub get
// http/core/dio_adapter.dart
import 'package:dio/dio.dart';
import 'package:flutter_bili_app/http/core/hi_error.dart';
import 'package:flutter_bili_app/http/core/hi_net_adapter.dart';
import 'package:flutter_bili_app/http/request/base_request.dart';
///Dio适配器
class DioAdapter extends HiNetAdapter {
@override
Future<HiNetResponse<T>> send<T>(BaseRequest request) async {
var response, options = Options(headers: request.header);
var error;
try {
if (request.httpMethod() == HttpMethod.GET) {
response = await Dio().get(request.url(), options: options);
} else if (request.httpMethod() == HttpMethod.POST) {
response = await Dio()
.post(request.url(), data: request.params, options: options);
} else if (request.httpMethod() == HttpMethod.DELETE) {
response = await Dio()
.delete(request.url(), data: request.params, options: options);
}
} on DioError catch (e) {
error = e;
response = e.response;
}
if (error != null) {
///抛出HiNetError
throw HiNetError(response?.statusCode ?? -1, error.toString(),
data: await buildRes(response, request));
}
return buildRes(response, request);
}
///构建HiNetResponse
Future<HiNetResponse<T>> buildRes<T>(
Response? response, BaseRequest request) {
return Future.value(HiNetResponse(
//?.防止response为空
data: response?.data,
request: request,
statusCode: response?.statusCode,
statusMessage: response?.statusMessage,
extra: response));
}
}
封装统一网络请求
// http/core/hi_net.dart
import 'package:flutter_bili_app/http/core/dio_adapter.dart';
import '../request/base_request.dart';
import 'hi_error.dart';
import 'hi_net_adapter.dart';
///1.支持网络库插拔设计,且不干扰业务层
///2.基于配置请求请求,简洁易用
///3.Adapter设计,扩展性强
///4.统一异常和返回处理
class HiNet {
HiNet._();
static HiNet? _instance;
static HiNet getInstance() {
if (_instance == null) {
_instance = HiNet._();
}
return _instance!;
}
Future fire(BaseRequest request) async {
HiNetResponse? response;
var error;
try {
response = await send(request);
} on HiNetError catch (e) {
error = e;
response = e.data;
printLog(e.message);
} catch (e) {
//其它异常
error = e;
printLog(e);
}
if (response == null) {
printLog(error);
}
var result = response?.data;
printLog(result);
var status = response?.statusCode;
switch (status) {
case 200:
return result;
case 401:
throw NeedLogin();
case 403:
throw NeedAuth(result.toString(), data: result);
default:
throw HiNetError(status ?? -1, result.toString(), data: result);
}
}
Future<HiNetResponse<T>> send<T>(BaseRequest request) async {
///使用Dio发送请求
HiNetAdapter adapter = DioAdapter();
return adapter.send(request);
}
void printLog(log) {
print('hi_net:' + log.toString());
}
}
封装测试接口
// http/request/test_request.dart
import 'package:flutter_bili_app/http/request/base_request.dart';
class TestRequest extends BaseRequest {
@override
HttpMethod httpMethod() {
return HttpMethod.GET;
}
@override
bool needLogin() {
return false;
}
@override
String path() {
return 'uapi/test/test';
}
}
页面中使用
import 'package:flutter_bili_app/http/core/hi_error.dart';
import 'package:flutter_bili_app/http/request/test_request.dart';
import 'dart:convert';
import 'http/core/hi_net.dart';
void _incrementCounter() async {
TestRequest request = TestRequest();
request.add("aa", "ddd").add("bb", "333").add("requestPrams", "kkkk");
try {
var result = await HiNet.getInstance().fire(request);
print(result);
} on NeedAuth catch (e) {
print(e);
} on NeedLogin catch (e) {
print(e);
} on HiNetError catch (e) {
print(e);
}
}
Dart JSON编码器和解码器剖析
概述
JSON(JavaScript对象表示法)是一种轻量级的数据交换格式。因为易于阅读和书写并且机器也很容易解析和生成,被广泛应用到客户端和服务器的数据交换中。
目前各大公司的服务端接口基本上都支持json格式的数据交换,支持json交换格式的接口会在http的响应头中包含一下信息:
Content-Type: application/json;charset=UTF-8
使用dart:convert进行json解析
Flutter采用Dart语言进行开发,那dart是如何进行json解析的呢?
在dart中有个内置的json解析器:dart:convert
,对于较小项目可以借助它来进行手动JSON序列化。
json转map
import 'dart:convert';
const jsonString =
"{ \"name\": \"flutter\", \"url\": \"https://coding.imooc.com/class/487.html\" }";
//json转map
Map<String, dynamic> jsonMap = jsonDecode(jsonString);
print('name:${jsonMap['name']}');
print('url:${jsonMap['url']}');
使用jsonDecode
方法,会将json字符串解析成Map<String, dynamic>,然后根据需要从map中查找所需的值就可以了,它没有外部依赖或其它的设置,对于小项目很方便。
map转json
String jsonStr = jsonEncode(jsonMap);
print('json:$jsonStr');
jsonEncode
可以很轻松的将object转成json字符串。
当您的项目变大时,手动编写序列化逻辑可能变得难以管理且容易出错。如果您在访问未提供的JSON字段时输入了一个错误的字段,则您的代码将会在运行时会引发错误。
如果您的项目中JSON model并不多,并且希望快速测试一下,那么手动序列化可能会很方便。
实体类
安装json_serializable 与 json_annotation 与 build_runner
插件:
pub.dev/packages/js… pub.dev/packages/js… pub.dev/packages/bu…
修改pubspec.yaml
dependencies:
json_serializable: ^6.7.0
json_annotation: ^4.8.1
dev_dependencies:
build_runner: ^2.4.5
配置实体类
// model/result.dart
// result.g.dart 将在我们运行生成命令后自动生成
import 'package:json_annotation/json_annotation.dart';
part 'result.g.dart';
@JsonSerializable()
class Result {
//定义字段
int code;
String method;
String requestPrams;
Result(this.code, this.method, this.requestPrams);
//固定格式,不同的类使用不同的mixin即可
factory Result.fromJson(Map<String, dynamic> json) => _$ResultFromJson(json);
//固定格式,
Map<String, dynamic> toJson() => _$ResultToJson(this);
}
因为实体类的生成代码还不存在,所以上代码会提示一些错误是正常现象 执行build生成实体类, 会生成result.g.dart
flutter packages pub run build_runner build
使用
import 'package:flutter_bili_app/model/result.dart';
const jsonString = {'code': 100, 'method': 'get', 'requestPrams': 'aabb'};
print(Result.fromJson(jsonString).toJson());
统一缓存管理框架hi_cache设计
安装shared_preferences
修改pubspec.yaml
shared_preferences: ^2.1.2
// db/hi_cache.dart
import 'package:shared_preferences/shared_preferences.dart';
///缓存管理类
class HiCache {
SharedPreferences? prefs;
HiCache._() {
init();
}
static HiCache? _instance;
HiCache._pre(SharedPreferences prefs) {
this.prefs = prefs;
}
///预初始化,防止在使用get时,prefs还未完成初始化
static Future<HiCache> preInit() async {
if (_instance == null) {
var prefs = await SharedPreferences.getInstance();
_instance = HiCache._pre(prefs);
}
return _instance!;
}
static HiCache getInstance() {
if (_instance == null) {
_instance = HiCache._();
}
return _instance!;
}
void init() async {
if (prefs == null) {
prefs = await SharedPreferences.getInstance();
}
}
setString(String key, String value) {
prefs?.setString(key, value);
}
setDouble(String key, double value) {
prefs?.setDouble(key, value);
}
setInt(String key, int value) {
prefs?.setInt(key, value);
}
setBool(String key, bool value) {
prefs?.setBool(key, value);
}
setStringList(String key, List<String> value) {
prefs?.setStringList(key, value);
}
remove(String key) {
prefs?.remove(key);
}
T? get<T>(String key) {
var result = prefs?.get(key);
if (result != null) {
return result as T;
}
return null;
}
}
使用
import 'package:flutter_bili_app/db/hi_cache.dart';
HiCache.getInstance().setString("aa", "1234");
var value = HiCache.getInstance().get("aa");
print('value:$value');
转载自:https://juejin.cn/post/7248073216513605690