网络层最佳实践
在 Flu CLI 生成的项目中,网络层不仅仅是一个 HTTP 客户端,更是一套完整的 API 交互解决方案。本文将介绍如何高效、优雅地使用这套网络架构。
1. 核心原则
依赖注入 (Dependency Injection)
在编写 Service 时,永远不要直接实例化 AppHttp,而是应该通过构造函数注入。这对于单元测试至关重要。
✅ 推荐写法:
class UserService {
final AppHttp _http;
// 允许外部注入 Mock 的 AppHttp
UserService({AppHttp? http}) : _http = http ?? AppHttp();
}❌ 避免写法:
class UserService {
// 强耦合,无法测试
final AppHttp _http = AppHttp();
}响应数据解包
AppResponse<T> 是统一的响应壳,Service 层应该负责"解包",只将业务数据(Data)或业务对象返回给 ViewModel。
✅ 推荐写法:
Future<User> getUserProfile() async {
final response = await _http.get('/profile');
if (response.isSuccess) {
// Service 层负责转模型
return User.fromJson(response.data);
}
throw ApiException(response.code, response.message);
}2. Mock 数据的使用策略
Mock 数据是前后端分离开发的神器。Flu CLI 的网络层内置了对 Mock 的原生支持。
开启 Mock
在 lib/config/app_config.dart (或类似配置入口) 中:
// 只有在开发环境才开启
if (!kReleaseMode) {
AppConfig.I.useMockData = true;
}编写 Mock 逻辑
在 Service 中,你可以优雅地处理 Mock 分支:
Future<List<Product>> getProducts() async {
// 1. Mock 分支
if (AppConfig.I.useMockData) {
// 模拟网络延迟,体验更真实
await Future.delayed(const Duration(milliseconds: 500));
return [
Product(id: 1, name: 'Mock Product A'),
Product(id: 2, name: 'Mock Product B'),
];
}
// 2. 真实网络请求
final response = await _http.get('/products');
// ... 处理响应
}3. 拦截器的高级用法
拦截器 (lib/core/network/interceptors) 是处理全局逻辑的最佳场所。
Token 自动注入
在 AuthInterceptor 中:
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
final token = UserManager.I.token;
if (token.isNotEmpty) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}全局错误处理
有时候后端会返回 200 OK,但业务 Code 是错误码(如 401 Token 失效)。你应该在 ErrorInterceptor 或响应拦截器中统一处理。
// 伪代码示例
if (response.data['code'] == 401) {
// 触发全局登出事件
EventBus.emit(LogoutEvent());
// 抛出特定异常,中断后续逻辑
throw UnauthenticatedException();
}4. 多环境配置
不要将 BaseURL 硬编码在代码中。推荐使用 Config 类配合 flutter build --dart-define 或编译配置。
class EnvConfig {
static const appName = String.fromEnvironment('APP_NAME', defaultValue: 'Flu App');
static const baseUrl = String.fromEnvironment('BASE_URL', defaultValue: 'https://api.dev.com');
}
// 初始化
AppConfig.I.init(baseUrl: EnvConfig.baseUrl);5. 常见误区 FAQ
Q: 为什么我的 Service 返回的是 AppResponse 而不是业务对象? A: 这是一个常见误区。ViewModel 不应该感知 HTTP 协议细节(如 code, message)。Service 应该消化掉 AppResponse,只吐出 User, List<Product> 这样的纯业务对象。
Q: 如何上传文件? A: Dio 原生支持 FormData。
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile('./text.txt', filename: 'upload.txt'),
});
await _http.post('/upload', data: formData);Q: 如何取消请求? A: 使用 CancelToken。页面销毁时记得取消未完成的请求,防止内存泄漏。