Flutter for OpenHarmony二手物品置换App实战 - 网络请求实现
本文介绍了Flutter中网络请求的封装方法,基于Dio库实现统一的网络请求管理。通过单例模式创建HttpClient类,配置基础URL、超时时间和请求头等通用设置。重点设计了三种拦截器:LogInterceptor用于开发调试,AuthInterceptor自动处理token认证和失效跳转,ErrorInterceptor统一管理各类网络异常并提供友好提示。最后封装了商品列表和详情API接口,通

网络请求是App与服务器交互的基础,获取商品列表、发布商品、用户登录等功能都需要网络请求。今天我们来讲解网络请求的封装和使用。
网络请求的设计思路
我们使用dio包进行网络请求,它功能强大、使用方便。封装一个统一的请求类,处理基础URL、请求头、错误处理等通用逻辑。
Dio的基本配置
import 'package:dio/dio.dart';
class HttpClient {
static final HttpClient _instance = HttpClient._internal();
factory HttpClient() => _instance;
late Dio _dio;
HttpClient._internal() {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
headers: {
'Content-Type': 'application/json',
},
));
以上代码完成了HttpClient单例类的核心初始化前置结构搭建。首先导入dio包,接着定义单例模式的核心结构,通过私有构造函数和工厂方法保证全局仅一个HttpClient实例,同时声明Dio对象并在私有构造中初始化基础配置,包含接口域名、超时时间和默认请求头,这些是所有网络请求的通用基础配置,确保请求的一致性。
// 添加拦截器
_dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
));
_dio.interceptors.add(AuthInterceptor());
_dio.interceptors.add(ErrorInterceptor());
}
Dio get dio => _dio;
}
这段代码为Dio实例添加了三个关键拦截器。LogInterceptor开启了请求体和响应体的日志打印,方便开发阶段调试网络请求的详细信息;AuthInterceptor用于处理认证相关逻辑,ErrorInterceptor负责统一拦截网络错误,拦截器的链式添加让请求在发送和响应阶段能被依次处理,实现通用逻辑的解耦。
这里用单例模式创建HttpClient,保证全局只有一个Dio实例。BaseOptions配置了基础URL和超时时间,所有请求都会使用这些默认配置。拦截器是Dio的强大功能,可以在请求发送前和响应返回后做统一处理。LogInterceptor用于开发调试,能打印请求和响应的详细信息。
认证拦截器
class AuthInterceptor extends Interceptor {
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
final token = await Storage.getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
此段代码实现了请求发送前的token注入逻辑。AuthInterceptor继承自Dio的Interceptor,重写onRequest方法,在每次请求发出前,从本地存储中异步获取用户token,若token存在则添加到请求头的Authorization字段中,采用Bearer认证方式,避免业务代码重复处理token添加逻辑。
void onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401) {
await Storage.removeToken();
Get.offAll(() => const LoginPage());
return;
}
handler.next(err);
}
}
这段代码处理认证失败的异常场景。当请求返回401状态码时,说明token失效或未授权,此时异步清除本地存储的token,并通过GetX路由框架跳转到登录页,且关闭所有之前的页面,确保用户必须重新登录才能继续操作,提升应用的安全性和用户体验。
认证拦截器自动处理token相关的逻辑。每次请求前从本地存储读取token并添加到请求头,这样业务代码就不用每次都手动加token了。当服务器返回401状态码时,说明token过期或无效,自动清除本地token并跳转到登录页,用户体验更流畅。
错误拦截器
class ErrorInterceptor extends Interceptor {
void onError(DioException err, ErrorInterceptorHandler handler) {
String message;
switch (err.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
message = '网络连接超时';
break;
case DioExceptionType.connectionError:
message = '网络连接失败';
break;
这段代码是错误拦截器的核心异常分类逻辑。ErrorInterceptor重写onError方法,首先定义提示消息变量,然后根据DioException的类型进行分类,针对超时类(连接、发送、接收超时)和连接错误类分别设置对应的用户友好提示文案,让用户能清晰知晓网络异常的具体类型。
case DioExceptionType.badResponse:
message = _handleStatusCode(err.response?.statusCode);
break;
default:
message = '网络异常';
}
Get.snackbar('错误', message, snackPosition: SnackPosition.BOTTOM);
handler.next(err);
}
此部分代码完成异常提示的展示和错误透传。针对服务器返回错误响应的情况,调用_handleStatusCode方法处理不同状态码;默认情况统一提示“网络异常”。随后通过GetX的snackbar在页面底部展示错误提示,最后调用handler.next(err)将错误继续传递,不影响后续可能的错误处理逻辑。
String _handleStatusCode(int? statusCode) {
switch (statusCode) {
case 400: return '请求参数错误';
case 403: return '拒绝访问';
case 404: return '请求地址不存在';
case 500: return '服务器内部错误';
default: return '请求失败';
}
}
}
这段代码是状态码的精细化处理方法。_handleStatusCode接收服务器返回的状态码,针对400(参数错误)、403(权限拒绝)、404(地址不存在)、500(服务器内部错误)等常见状态码,返回对应的中文提示,其他状态码统一提示“请求失败”,让错误提示更精准,帮助用户和开发人员定位问题。
错误拦截器统一处理各种网络异常,给用户友好的提示信息。超时、断网、服务器错误等情况都有对应的提示文案。这样业务代码只需要关注正常流程,异常情况由拦截器统一处理,代码更简洁。
API封装
class Api {
static final _http = HttpClient().dio;
static Future<List<Map<String, dynamic>>> getProducts({
int page = 1,
int pageSize = 20,
String? category,
String? keyword,
}) async {
final response = await _http.get('/products', queryParameters: {
'page': page,
'pageSize': pageSize,
if (category != null) 'category': category,
这段代码是商品列表接口的封装核心。首先定义Api类并获取全局Dio实例,然后封装getProducts静态方法,返回Future类型的商品列表数据。方法设置了分页(page、pageSize)、分类(category)、关键词(keyword)等可选参数,其中分页参数有默认值,通过get请求访问/products接口,并利用Dart的条件语法向queryParameters中添加非空的分类和关键词参数,避免传递空参数导致接口异常。
if (keyword != null) 'keyword': keyword,
});
return List<Map<String, dynamic>>.from(response.data['list']);
}
static Future<Map<String, dynamic>> getProductDetail(int id) async {
final response = await _http.get('/products/$id');
return response.data;
}
此部分封装了商品详情接口和完善了列表接口的返回处理。列表接口中,请求成功后将响应数据中的list字段转换为List<Map<String, dynamic>>类型返回;商品详情接口接收商品id参数,通过get请求访问/products/$id接口,直接返回响应的原始数据,满足详情页对完整商品信息的需求。
static Future<void> publishProduct({
required String title,
required String description,
required double price,
required String category,
required List<String> images,
}) async {
await _http.post('/products', data: {
'title': title,
'description': description,
'price': price,
'category': category,
'images': images,
});
}
这段代码封装了发布商品的接口。publishProduct方法接收标题、描述、价格、分类、图片列表等必填参数,通过post请求访问/products接口,将参数封装为JSON格式的请求体发送给服务器,方法返回Future,因为发布操作只需关注是否成功,无需返回具体数据。
static Future<void> addFavorite(int productId) async {
await _http.post('/favorites', data: {'productId': productId});
}
static Future<void> removeFavorite(int productId) async {
await _http.delete('/favorites/$productId');
}
此部分封装了收藏和取消收藏商品的接口。addFavorite通过post请求向/favorites接口发送商品id,实现收藏功能;removeFavorite通过delete请求访问/favorites/$productId接口,根据商品id取消收藏,两个方法均返回Future,符合这类无返回数据的接口设计逻辑。
static Future<Map<String, dynamic>> login(String phone, String code) async {
final response = await _http.post('/auth/login', data: {
'phone': phone,
'code': code,
});
return response.data;
}
}
这段代码封装了用户登录接口。login方法接收手机号和验证码参数,通过post请求访问/auth/login接口,将参数作为请求体发送,请求成功后返回响应数据,其中包含登录成功后的token、用户信息等关键数据,为后续的认证逻辑提供支撑。
把所有API封装成静态方法,调用时直接Api.getProducts(),简洁明了。参数用命名参数的方式,可读性更好。Dart的集合字面量支持条件添加元素,if (category != null) 'category': category这种写法很优雅,避免了传空值的问题。
在页面中使用
class _HomePageState extends State<HomePage> {
List<Map<String, dynamic>> _products = [];
bool _isLoading = true;
int _page = 1;
bool _hasMore = true;
void initState() {
super.initState();
_loadProducts();
}
Future<void> _loadProducts() async {
try {
setState(() => _isLoading = true);
final data = await Api.getProducts(page: _page);
这段代码是首页状态管理和初始数据加载的核心。首先在_HomePageState中定义商品列表、加载状态、当前页码、是否有更多数据等状态变量;在initState生命周期方法中调用_loadProducts方法,实现页面初始化时自动加载第一页数据。_loadProducts方法中,先将加载状态置为true,再调用Api.getProducts获取对应页码的商品数据。
setState(() {
_products = data;
_isLoading = false;
_hasMore = data.length >= 20;
});
} catch (e) {
setState(() => _isLoading = false);
}
}
此部分完成商品列表数据的状态更新和异常处理。请求成功后,将返回的商品数据赋值给_products,加载状态置为false,并根据返回数据的长度判断是否有更多数据(若长度≥20则认为还有更多);若请求过程中出现异常,仅将加载状态置为false,错误提示由拦截器统一处理,保证页面状态稳定。
Future<void> _loadMoreProducts() async {
if (!_hasMore) return;
try {
_page++;
final data = await Api.getProducts(page: _page);
setState(() {
_products.addAll(data);
_hasMore = data.length >= 20;
});
} catch (e) {
_page--;
}
}
}
这段代码实现了商品列表的加载更多功能。首先判断_hasMore,若无更多数据则直接返回;若有则页码自增,调用接口获取下一页数据,请求成功后将新数据添加到现有列表中,并更新_hasMore状态;若请求失败,将页码回退,保证下次加载更多时能重新请求当前页码,避免页码错乱导致的数据异常。
页面中调用API获取数据,用状态变量控制加载状态和分页。_hasMore根据返回数据量判断是否还有更多数据,避免无意义的请求。加载更多失败时把页码减回去,下次还能重试。错误提示由拦截器统一处理,这里只需要关注状态更新。
小结
好的网络请求封装能大大提高开发效率。拦截器处理通用逻辑,API类封装具体接口,页面只关注业务流程。这种分层设计让代码职责清晰,易于维护和扩展。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)