Flutter dio http 封装指南说明
视频
https://www.bilibili.com/video/BV1qy41187y3/
前言
原文 https://ducafecat.com/blog/building-a-powerful-flutter-dio-wrapper
本文介绍了如何实现一个通用、可重构的 Dio 基础类,包括单例访问、日志记录、常见操作封装以及请求、输出、报错拦截等功能。
Flutter, Dio, 网络请求, 封装, 重构, 可扩展
参考
https://pub.dev/packages/pretty_dio_logger
https://github.com/cfug/dio/blob/main/dio/README-ZH.md
步骤
第一步:安装 dio 插件
pubspec.yaml
dependencies:
...
dio: ^5.4.3+1
dev_dependencies:
...
pretty_dio_logger: ^1.3.1
第二步:编写单例工具类
lib/utils/woo_http.dart
常量
const String APPLICATION_JSON = "application/json";
const String CONTENT_TYPE = "content-type";
const String ACCEPT = "accept";
const String AUTHORIZATION = "authorization";
const String DEFAULT_LANGUAGE = "en";
const String TOKEN = "Bearer token";
const String BASE_URL = "https://wpapi.ducafecat.tech";
单例类
/// woo 电商 api 请求工具类
class WooHttpUtil {
static final WooHttpUtil _instance = WooHttpUtil._internal();
factory WooHttpUtil() => _instance;
late Dio _dio;
/// 单例初始
WooHttpUtil._internal() {
// header 头
Map<String, String> headers = {
CONTENT_TYPE: APPLICATION_JSON,
ACCEPT: APPLICATION_JSON,
AUTHORIZATION: TOKEN,
DEFAULT_LANGUAGE: DEFAULT_LANGUAGE
};
// 初始选项
var options = BaseOptions(
baseUrl: BASE_URL,
headers: headers,
connectTimeout: const Duration(seconds: 5), // 5秒
receiveTimeout: const Duration(seconds: 3), // 3秒
responseType: ResponseType.json,
);
// 初始 dio
_dio = Dio(options);
// 拦截器 - 日志打印
if (!kReleaseMode) {
_dio.interceptors.add(PrettyDioLogger(
requestHeader: true,
requestBody: true,
responseHeader: true,
));
}
}
}
第三步:加入操作方法
常用的 get post put delete
/// get 请求
Future<Response> get(
String url, {
Map<String, dynamic>? params,
Options? options,
CancelToken? cancelToken,
}) async {
Options requestOptions = options ?? Options();
Response response = await _dio.get(
url,
queryParameters: params,
options: requestOptions,
cancelToken: cancelToken,
);
return response;
}
/// post 请求
Future<Response> post(
String url, {
dynamic data,
Options? options,
CancelToken? cancelToken,
}) async {
var requestOptions = options ?? Options();
Response response = await _dio.post(
url,
data: data ?? {},
options: requestOptions,
cancelToken: cancelToken,
);
return response;
}
/// put 请求
Future<Response> put(
String url, {
dynamic data,
Options? options,
CancelToken? cancelToken,
}) async {
var requestOptions = options ?? Options();
Response response = await _dio.put(
url,
data: data ?? {},
options: requestOptions,
cancelToken: cancelToken,
);
return response;
}
/// delete 请求
Future<Response> delete(
String url, {
dynamic data,
Options? options,
CancelToken? cancelToken,
}) async {
var requestOptions = options ?? Options();
Response response = await _dio.delete(
url,
data: data ?? {},
options: requestOptions,
cancelToken: cancelToken,
);
return response;
}
第四步:拦截器
安装拦截器
class WooHttpUtil {
...
/// 单例初始
WooHttpUtil._internal() {
...
// 拦截器
_dio.interceptors.add(RequestInterceptors());
}
拦截类
/// 拦截
class RequestInterceptors extends Interceptor {
//
/// 发送请求
/// 我们这里可以添加一些公共参数,或者对参数进行加密
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// super.onRequest(options, handler);
// http header 头加入 Authorization
// if (UserService.to.hasToken) {
// options.headers['Authorization'] = 'Bearer ${UserService.to.token}';
// }
return handler.next(options);
// 如果你想完成请求并返回一些自定义数据,你可以resolve一个Response对象 `handler.resolve(response)`。
// 这样请求将会被终止,上层then会被调用,then中返回的数据将是你的自定义response.
//
// 如果你想终止请求并触发一个错误,你可以返回一个`DioError`对象,如`handler.reject(error)`,
// 这样请求将被中止并触发异常,上层catchError会被调用。
}
/// 响应
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
// 200 请求成功, 201 添加成功
if (response.statusCode != 200 && response.statusCode != 201) {
handler.reject(
DioException(
requestOptions: response.requestOptions,
response: response,
type: DioExceptionType.badResponse,
),
true,
);
} else {
handler.next(response);
}
}
// // 退出并重新登录
// Future<void> _errorNoAuthLogout() async {
// await UserService.to.logout();
// IMService.to.logout();
// Get.toNamed(RouteNames.systemLogin);
// }
/// 错误
@override
Future<void> onError(
DioException err, ErrorInterceptorHandler handler) async {
final exception = HttpException(err.message ?? "error message");
switch (err.type) {
case DioExceptionType.badResponse: // 服务端自定义错误体处理
{
final response = err.response;
final errorMessage = ErrorMessageModel.fromJson(response?.data);
switch (errorMessage.statusCode) {
// 401 未登录
case 401:
// 注销 并跳转到登录页面
// _errorNoAuthLogout();
break;
case 404:
break;
case 500:
break;
case 502:
break;
default:
break;
}
// 显示错误信息
// if(errorMessage.message != null){
// Loading.error(errorMessage.message);
// }
}
break;
case DioExceptionType.unknown:
break;
case DioExceptionType.cancel:
break;
case DioExceptionType.connectionTimeout:
break;
default:
break;
}
DioException errNext = err.copyWith(
error: exception,
);
handler.next(errNext);
}
}
自定义错误消息 entity 类
lib/models/error_message_model.dart
/// 错误体信息
class ErrorMessageModel {
int? statusCode;
String? error;
String? message;
ErrorMessageModel({this.statusCode, this.error, this.message});
factory ErrorMessageModel.fromJson(Map<String, dynamic> json) {
return ErrorMessageModel(
statusCode: json['statusCode'] as int?,
error: json['error'] as String?,
message: json['message'] as String?,
);
}
Map<String, dynamic> toJson() => {
'statusCode': statusCode,
'error': error,
'message': message,
};
}
第五步:准备 entity 类
安装插件
https://marketplace.visualstudio.com/items?itemName=hirantha.json-to-dart
复制你的输出 json 数据
执行 json to dart 命令,转换来自于剪贴板
保存输出结果 lib/models/product_model
第六步:编写 api 类
lib/apis/product.dart
/// 商品 api
class ProductApi {
/// 商品列表
static Future<List<ProductModel>> list({int? page, int? prePage}) async {
var res = await WooHttpUtil().get('/products', params: {
'page': page ?? 1,
'per_page': prePage ?? 20,
});
List<ProductModel> items = [];
for (var item in res.data) {
items.add(ProductModel.fromJson(item));
}
return items;
}
}
最后:拉取数据
lib/pages/first.dart
class FirstPage extends StatefulWidget {
const FirstPage({super.key});
@override
State<FirstPage> createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
List<ProductModel> _products = [];
Future<void> _getProducts() async {
var products = await ProductApi.list();
if (mounted) {
setState(() {
_products = products;
});
}
}
@override
void initState() {
super.initState();
_getProducts();
}
Widget _buildView() {
return ListView.builder(
itemCount: _products.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_products[index].name ?? ""),
subtitle: Text(_products[index].description ?? ""),
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('First Page'),
),
body: _buildView(),
);
}
}
代码
https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_dio
小结
本文详细介绍了如何构建一个通用、可重构的 Flutter Dio 基础类,包括单例访问、日志记录、常见操作封装以及请求、输出、报错拦截等功能。通过这种封装,可以大大提高网络请求的可维护性和扩展性,并且可以轻松应对各种网络请求场景。希望本文能为您在 Flutter 开发中的网络请求部分提供有价值的参考和指导。
感谢阅读本文
如果有什么建议,请在评论中让我知道。我很乐意改进。
flutter 学习路径
- Flutter 优秀插件推荐 https://flutter.ducafecat.com
- Flutter 基础篇1 - Dart 语言学习 https://ducafecat.com/course/dart-learn
- Flutter 基础篇2 - 快速上手 https://ducafecat.com/course/flutter-quickstart-learn
- Flutter 实战1 - Getx Woo 电商APP https://ducafecat.com/course/flutter-woo
- Flutter 实战2 - 上架指南 Apple Store、Google Play https://ducafecat.com/course/flutter-upload-apple-google
- Flutter 基础篇3 - 仿微信朋友圈 https://ducafecat.com/course/flutter-wechat
- Flutter 实战3 - 腾讯即时通讯 第一篇 https://ducafecat.com/course/flutter-tim
- Flutter 实战4 - 腾讯即时通讯 第二篇 https://ducafecat.com/course/flutter-tim-s2
© 猫哥 ducafecat.com
end