一、添加依赖
在yaml文件里边添加如下依赖
dependencies:
dio: ^4.0.4
二、添加配置文件
新建一个network_config.dart文件存放网络配置
class NetWorkConfig {
static String baseUrl =
"https://www.fastmock.site/mock/d8bfa0e1c67bfd329a33ae3294d98c78/test";
static const connectTimeOut = 10000;
static const readTimeOut = 10000;
static const writeTimeOut = 10000;
static const successCode = 200;
}
三、请求封装
ApiResponse是之前定义的公共接口返回实体Flutter的Json数据解析之FlutterJsonBeanFactory插件
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter_core/network/network_config.dart';
import '../models/api_response/api_response_entity.dart';
import '../models/api_response/raw_data.dart';
import 'exception.dart';
class RequestClient {
late Dio _dio;
static final RequestClient _singletonRequestClient =
RequestClient._internal();
factory RequestClient() {
return _singletonRequestClient;
}
RequestClient._internal() {
///初始化 dio 配置
var options = BaseOptions(
baseUrl: NetWorkConfig.baseUrl,
connectTimeout: NetWorkConfig.connectTimeOut,
receiveTimeout: NetWorkConfig.readTimeOut,
sendTimeout: NetWorkConfig.writeTimeOut);
_dio = Dio(options);
}
/// dio 本身提供了get 、post 、put 、delete 等一系列 http 请求方法,最终都是调用request,直接封装request就行
Future<T?> request<T>(
String url, {
required String method,
Map<String, dynamic>? queryParameters,
dynamic data,
Map<String, dynamic>? headers,
bool Function(ApiException)? onError, //用于错误信息处理的回调
}) async {
try {
Options options = Options()
..method = method
..headers = headers;
data = _convertRequestData(data);
Response response = await _dio.request(url,
queryParameters: queryParameters, data: data, options: options);
return _handleResponse<T>(response);
} catch (e) {
///创建 ApiException ,调用 onError,当 onError 返回为 true 时即错误信息已被调用方处理,则不抛出异常,否则抛出异常。
var exception = ApiException.from(e);
if (onError?.call(exception) != true) {
throw exception;
}
}
return null;
}
///将请求 data 数据先使用 jsonEncode 转换为字符串,再使用 jsonDecode 方法将字符串转换为 Map。
_convertRequestData(data) {
if (data != null) {
data = jsonDecode(jsonEncode(data));
}
return data;
}
///请求响应内容处理
T? _handleResponse<T>(Response response) {
if (response.statusCode == 200) {
//判断泛型是否为 RawData ,是则直接把 response.data 放入 RawData 中返回,
//即 RawData 的 value 就是接口返回的原始数据。用于第三方平台的接口请求,返回数据接口不支持定义的ApiResponse的情况
if (T.toString() == (RawData).toString()) {
RawData raw = RawData();
raw.value = response.data;
return raw as T;
} else {
ApiResponse<T> apiResponse = ApiResponse<T>.fromJson(response.data);
return _handleBusinessResponse<T>(apiResponse);
}
} else {
var exception =
ApiException(response.statusCode, ApiException.unknownException);
throw exception;
}
}
///业务内容处理
T? _handleBusinessResponse<T>(ApiResponse<T> response) {
if (response.code == NetWorkConfig.successCode) {
return response.data;
} else {
var exception = ApiException(response.code, response.message);
throw exception;
}
}
Future<T?> get<T>(
String url, {
Map<String, dynamic>? queryParameters,
Map<String, dynamic>? headers,
bool showLoading = true,
bool Function(ApiException)? onError,
}) {
return request(url,
method: "Get",
queryParameters: queryParameters,
headers: headers,
onError: onError);
}
Future<T?> post<T>(
String url, {
Map<String, dynamic>? queryParameters,
data,
Map<String, dynamic>? headers,
bool showLoading = true,
bool Function(ApiException)? onError,
}) {
return request(url,
method: "POST",
queryParameters: queryParameters,
data: data,
headers: headers,
onError: onError);
}
Future<T?> delete<T>(
String url, {
Map<String, dynamic>? queryParameters,
data,
Map<String, dynamic>? headers,
bool showLoading = true,
bool Function(ApiException)? onError,
}) {
return request(url,
method: "DELETE",
queryParameters: queryParameters,
data: data,
headers: headers,
onError: onError);
}
Future<T?> put<T>(
String url, {
Map<String, dynamic>? queryParameters,
data,
Map<String, dynamic>? headers,
bool showLoading = true,
bool Function(ApiException)? onError,
}) {
return request(url,
method: "PUT",
queryParameters: queryParameters,
data: data,
headers: headers,
onError: onError);
}
}
四、异常处理
主要是对http异常和业务异常进行处理。
import 'package:dio/dio.dart';
import 'package:flutter_core/models/api_response/api_response_entity.dart';
class ApiException implements Exception {
static const unknownException = "未知错误";
final String? message;
final int? code;
String? stackInfo;
ApiException([this.code, this.message]);
factory ApiException.fromDioError(DioError error) {
switch (error.type) {
case DioErrorType.cancel:
return BadRequestException(-1, "请求取消");
case DioErrorType.connectTimeout:
return BadRequestException(-1, "连接超时");
case DioErrorType.sendTimeout:
return BadRequestException(-1, "请求超时");
case DioErrorType.receiveTimeout:
return BadRequestException(-1, "响应超时");
case DioErrorType.response:
try {
/// http错误码带业务错误信息
ApiResponse apiResponse = ApiResponse.fromJson(error.response?.data);
if (apiResponse.code != null) {
return ApiException(apiResponse.code, apiResponse.message);
}
int? errCode = error.response?.statusCode;
switch (errCode) {
case 400:
return BadRequestException(errCode, "请求语法错误");
case 401:
return UnauthorisedException(errCode!, "没有权限");
case 403:
return UnauthorisedException(errCode!, "服务器拒绝执行");
case 404:
return UnauthorisedException(errCode!, "无法连接服务器");
case 405:
return UnauthorisedException(errCode!, "请求方法被禁止");
case 500:
return UnauthorisedException(errCode!, "服务器内部错误");
case 502:
return UnauthorisedException(errCode!, "无效的请求");
case 503:
return UnauthorisedException(errCode!, "服务器异常");
case 505:
return UnauthorisedException(errCode!, "不支持HTTP协议请求");
default:
return ApiException(
errCode, error.response?.statusMessage ?? '未知错误');
}
} on Exception {
return ApiException(-1, unknownException);
}
default:
return ApiException(-1, error.message);
}
}
factory ApiException.from(dynamic exception) {
if (exception is DioError) {
return ApiException.fromDioError(exception);
}
if (exception is ApiException) {
return exception;
} else {
var apiException = ApiException(-1, unknownException);
apiException.stackInfo = exception?.toString();
return apiException;
}
}
}
/// 请求错误
class BadRequestException extends ApiException {
BadRequestException([int? code, String? message]) : super(code, message);
}
/// 未认证异常
class UnauthorisedException extends ApiException {
UnauthorisedException([int code = -1, String message = ''])
: super(code, message);
}
五、嵌套请求
上述封装后,如果业务存在多个请求依赖调用,就需要统一的处理错误。
//先定义一个顶级的request
Future request(Function() block, {bool showLoading = true, bool Function(ApiException)? onError, }) async{
try {
await loading(block, isShowLoading: showLoading);
} catch (e) {
handleException(ApiException.from(e), onError: onError);
}
return;
}
//统一的错误处理
bool handleException(ApiException exception,
{bool Function(ApiException)? onError}) {
if (onError?.call(exception) == true) {
return true;
}
//当外部未处理异常时则在 handleException 中进行统一处理
//todo 错误码统一处理
// if(exception.code == 401 ){
//
// return true;
// }
EasyLoading.showError(exception.message ?? ApiException.unknownException);
return false;
}
//请求的dialog
Future loading( Function block, {bool isShowLoading = true}) async{
if (isShowLoading) {
showLoading();
}
try {
await block();
} catch (e) {
rethrow;
} finally {
dismissLoading();
}
return;
}
void showLoading(){
EasyLoading.show(status: "加载中...");
}
void dismissLoading(){
EasyLoading.dismiss();
}
六、拦截器
Dio支持自定义拦截器,继承Interceptor
,重写onRequest
和onResponse
方法就行。
class HeadInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
super.onRequest(options, handler);
}
@override
void onResponse(dio.Response response, ResponseInterceptorHandler handler) {
super.onResponse(response, handler);
}
}
在初始化dio的地方,把拦截器加入dio对象的拦截器集合dio.interceptors
中就行。
七、日志打印
可以通过自定义的拦截器实现,也可以引入pretty_dio_logger
库。
dependencies:
pretty_dio_logger: ^1.1.1
_dio.interceptors.add(PrettyDioLogger(
requestHeader: true,
requestBody: true,
responseHeader: true,
responseBody: true));
八、模拟请求
fastmock上新建自己的项目,接口配置如下:
{
code: ({
_req,
Mock
}) => {
let body = _req.body;
return body.username === "qi" && body.password === "123456" ?
200 :
1000001;
},
message: ({
_req,
Mock
}) => {
let body = _req.body;
return body.username === "qi" && body.password === "123456" ?
"success" :
"请确认账号密码后再次重试";
},
data: function({
_req,
Mock
}) {
let body = _req.body;
if (body.username === "qi" && body.password === "123456") {
return Mock.mock({
username: "qi",
userId: "123456",
email: "@email",
address: "@address",
"age|10-30": 18,
});
} else {
return null
}
},
};
发起请求:
void login() =>
request(() async {
LoginParams params = LoginParams();
params.username = "qi";
params.password = "123456";
UserEntity? user =
await apiService.login(params);
state.user = user;
debugPrint("-------------${user?.username ?? "登录失败"}");
update();
}, onError: (ApiException e) {
state.errorMessage = "request error : ${e.message}";
debugPrint(state.errorMessage);
update();
return true;
});
void loginError() =>
request(() async {
LoginParams params = LoginParams();
params.username = "qi";
params.password = "123";
UserEntity? user =
await apiService.login(params);
state.user = user;
debugPrint("-------------${user?.username ?? "登录失败"}");
update();
},onError: (ApiException e) {
state.errorMessage = "request error : ${e.message}";
debugPrint(state.errorMessage);
update();
if (e.code == 1000001) {
return false;
} else {
return true;
}
});
效果展示:
参考文章:
https://juejin.cn/post/7061806192980410382
https://juejin.cn/post/7043721908801503269