Flutter dio http 封裝指南說明
視頻
https://www.bilibili.com/video/BV1qy41187y3/
前言
原文 https://ducafecat.com/blog/building-a-powerful-flutter-dio-wrapper
本文介紹了如何實現(xiàn)一個通用、可重構(gòu)的 Dio 基礎(chǔ)類,包括單例訪問凑术、日志記錄寄月、常見操作封裝以及請求蜀备、輸出佳遣、報錯攔截等功能宛瞄。
Flutter, Dio, 網(wǎng)絡(luò)請求, 封裝, 重構(gòu), 可擴展
參考
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 {
//
/// 發(fā)送請求
/// 我們這里可以添加一些公共參數(shù)捻脖,或者對參數(shù)進行加密
@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);
// 如果你想完成請求并返回一些自定義數(shù)據(jù)鸠窗,你可以resolve一個Response對象 `handler.resolve(response)`苛坚。
// 這樣請求將會被終止澈缺,上層then會被調(diào)用,then中返回的數(shù)據(jù)將是你的自定義response.
//
// 如果你想終止請求并觸發(fā)一個錯誤,你可以返回一個`DioError`對象,如`handler.reject(error)`炕婶,
// 這樣請求將被中止并觸發(fā)異常姐赡,上層catchError會被調(diào)用。
}
/// 響應(yīng)
@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: // 服務(wù)端自定義錯誤體處理
{
final response = err.response;
final errorMessage = ErrorMessageModel.fromJson(response?.data);
switch (errorMessage.statusCode) {
// 401 未登錄
case 401:
// 注銷 并跳轉(zhuǎn)到登錄頁面
// _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,
};
}
第五步:準(zhǔn)備 entity 類
安裝插件
https://marketplace.visualstudio.com/items?itemName=hirantha.json-to-dart
復(fù)制你的輸出 json 數(shù)據(jù)
執(zhí)行 json to dart 命令柠掂,轉(zhuǎn)換來自于剪貼板
保存輸出結(jié)果 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;
}
}
最后:拉取數(shù)據(jù)
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
小結(jié)
本文詳細介紹了如何構(gòu)建一個通用项滑、可重構(gòu)的 Flutter Dio 基礎(chǔ)類,包括單例訪問、日志記錄涯贞、常見操作封裝以及請求枪狂、輸出、報錯攔截等功能宋渔。通過這種封裝,可以大大提高網(wǎng)絡(luò)請求的可維護性和擴展性,并且可以輕松應(yīng)對各種網(wǎng)絡(luò)請求場景州疾。希望本文能為您在 Flutter 開發(fā)中的網(wǎng)絡(luò)請求部分提供有價值的參考和指導(dǎo)。
感謝閱讀本文
如果有什么建議皇拣,請在評論中讓我知道严蓖。我很樂意改進。
flutter 學(xué)習(xí)路徑
- Flutter 優(yōu)秀插件推薦 https://flutter.ducafecat.com
- Flutter 基礎(chǔ)篇1 - Dart 語言學(xué)習(xí) https://ducafecat.com/course/dart-learn
- Flutter 基礎(chǔ)篇2 - 快速上手 https://ducafecat.com/course/flutter-quickstart-learn
- Flutter 實戰(zhàn)1 - Getx Woo 電商APP https://ducafecat.com/course/flutter-woo
- Flutter 實戰(zhàn)2 - 上架指南 Apple Store氧急、Google Play https://ducafecat.com/course/flutter-upload-apple-google
- Flutter 基礎(chǔ)篇3 - 仿微信朋友圈 https://ducafecat.com/course/flutter-wechat
- Flutter 實戰(zhàn)3 - 騰訊即時通訊 第一篇 https://ducafecat.com/course/flutter-tim
- Flutter 實戰(zhàn)4 - 騰訊即時通訊 第二篇 https://ducafecat.com/course/flutter-tim-s2
? 貓哥
ducafecat.com
end