Flutter Repository Pattern
很久不写 Flutter 了,最近在写一个项目,用到了 Repository Pattern,然后也用到了 riverpod,还记得当时学 flutter 时候用的 provider,然后其他项目又用到了 bloc,现在又用到了 riverpod,感觉 flutter 的生态真的是太丰富了。
全局使用 riverpod,太方便了,想到就用,直接 ref 一把嗦,爽歪歪。然后本地状态用 flutter_hooks,配合起来用效果更好。
3.7
用 claude 3.7 解析,然后生成流程图。
是的,你说得对!WaterDio
是实际处理 HTTP 请求的核心组件,让我更新完整的流程图:
完整的文件结构:
lib/
├── app/
│ └── pages/
│ └── auth/
│ └── login_user.dart # UI 层
├── data/
│ ├── data_providers.dart # Provider 定义
│ ├── repositories/
│ │ ├── admin_api_repository.dart # 接口定义
│ │ └── implements/
│ │ └── admin_api_implements.dart # 接口实现
│ ├── sources/
│ │ └── xxx_project_api_source.dart # API 调用封装
│ ├── dio/
│ │ ├── xxx_dio.dart # Dio 实现
│ │ └── base_dio.dart # 基础 Dio 配置
│ └── models/
│ └── auth/
│ ├── login_request.dart # 请求模型
│ └── login_response.dart # 响应模型
_BaseDio
通常包含的功能:
class _BaseDio {
late final Dio _dio;
_BaseDio() {
_dio = Dio()
..options = BaseOptions(
baseUrl: 'API_BASE_URL',
connectTimeout: Duration(seconds: 30),
receiveTimeout: Duration(seconds: 30),
)
..interceptors.addAll([
// 日志拦截器
LogInterceptor(),
// 错误处理拦截器
ErrorInterceptor(),
// Token 拦截器
TokenInterceptor(),
// 重试拦截器
RetryInterceptor(),
]);
}
}
UserDio
的特定实现:
class UserDio extends _BaseDio {
// 用户相关的特定配置
UserDio() {
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
// 添加用户相关的 headers
options.headers['Authorization'] = 'Bearer $userToken';
return handler.next(options);
},
),
);
}
// 封装具体的请求方法
Future<Response<T>> get<T>(...) => _dio.get(...);
Future<Response<T>> post<T>(...) => _dio.post(...);
}
完整的调用链:
- UI 触发请求
- Repository 处理业务逻辑
- ApiSource 封装 API 调用
- UserDio 处理 HTTP 请求
- 请求拦截(headers、token 等)
- 错误处理
- 重试机制
- 日志记录
- 服务器响应
- 响应处理(数据转换、错误处理)
- 返回业务数据
这种架构的优势:
- 统一的网络层配置
- 灵活的拦截器机制
- 统一的错误处理
- 可配置的重试策略
- 完整的日志系统
o1 基础介绍
简单扼要的介绍下 flutter 的 resposity pattern 网络请求模式,用 retrofit。业务层用 freezed,riverpod annotation,riverpod hooks,flutter hook
下面给出一个简要示例,演示如何在 Flutter 中结合 retrofit
(网络请求)、freezed
(数据类)、riverpod_annotation
(Riverpod 代码生成)、riverpod_hooks
/ flutter_hooks
(Hooks)构建一套「Repository + Provider」的数据请求层级。代码示例仅作思路参考,实际业务可根据需要扩充。
1. 添加依赖
在 pubspec.yaml
中(示例):
dependencies:
flutter:
sdk: flutter
dio: ^5.1.0
retrofit: ^4.0.1
json_annotation: ^4.8.0
freezed_annotation: ^2.2.0
hooks_riverpod: ^2.3.6
flutter_hooks: ^0.18.6
dev_dependencies:
build_runner: ^2.3.3
retrofit_generator: ^7.0.1
json_serializable: ^6.6.0
freezed: ^2.2.0
riverpod_annotation: ^2.0.2
2. 定义数据模型(freezed
)
以「用户信息」为例,结合 freezed
+ json_serializable
来简化序列化。
// user.dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
class User with _$User {
const factory User({
required String id,
required String name,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
执行 flutter pub run build_runner build
后,会生成 user.freezed.dart
和 user.g.dart
。
3. 定义 Retrofit 接口
使用 retrofit
+ dio
:
// api_service.dart
import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';
import 'user.dart';
part 'api_service.g.dart';
(baseUrl: "https://example.com/api")
abstract class ApiService {
factory ApiService(Dio dio, {String baseUrl}) = _ApiService;
("/users/{id}")
Future<User> getUser(("id") String id);
("/users")
Future<List<User>> getUsers();
}
同样执行 flutter pub run build_runner build
来生成 api_service.g.dart
。
4. 定义 Repository 接口和实现
4.1. Repository 接口
// user_repository.dart
import 'user.dart';
abstract class UserRepository {
Future<User> fetchUserById(String id);
Future<List<User>> fetchAllUsers();
}
4.2. 具体实现
// user_repository_impl.dart
import 'package:dio/dio.dart';
import 'api_service.dart';
import 'user.dart';
import 'user_repository.dart';
class UserRepositoryImpl implements UserRepository {
final ApiService _apiService;
UserRepositoryImpl(Dio dio) : _apiService = ApiService(dio);
Future<User> fetchUserById(String id) => _apiService.getUser(id);
Future<List<User>> fetchAllUsers() => _apiService.getUsers();
}
5. 在 Riverpod 中声明 Provider
利用 riverpod_annotation
(自动生成 Provider),示例中声明了一个 UserRepository
的 Provider,以及一个获取用户列表的异步 Provider。
// user_providers.dart
import 'package:dio/dio.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'user.dart';
import 'user_repository.dart';
import 'user_repository_impl.dart';
part 'user_providers.g.dart';
// 1) 声明一个 Repository Provider
UserRepository userRepository(UserRepositoryRef ref) {
final dio = Dio(); // 如果有全局 dio 也可从别的 provider 里获取
return UserRepositoryImpl(dio);
}
// 2) 声明一个异步 Provider,用于获取用户列表
Future<List<User>> userList(UserListRef ref) {
return ref.watch(userRepositoryProvider).fetchAllUsers();
}
// 你也可以声明获取单个用户的 Provider,或更多业务逻辑
执行 flutter pub run build_runner build
后,会生成 user_providers.g.dart
。
这样就有了:
userRepositoryProvider
:返回UserRepositoryImpl
实例userListProvider
:返回一个异步Future<List<User>>
,依赖userRepositoryProvider
6. 在界面层(Widget)中使用 Hooks + Riverpod
在 Flutter 中,可以使用 HookConsumerWidget
来消费 Riverpod Provider,并结合 Hooks 管理状态、生命周期等。示例:
// user_list_page.dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'user_providers.dart';
class UserListPage extends HookConsumerWidget {
const UserListPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final asyncUserList = ref.watch(userListProvider);
return Scaffold(
appBar: AppBar(title: const Text("User List")),
body: asyncUserList.when(
data: (users) => ListView.builder(
itemCount: users.length,
itemBuilder: (_, index) {
final user = users[index];
return ListTile(
title: Text(user.name),
subtitle: Text('ID: ${user.id}'),
);
},
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, st) => Center(child: Text("Error: $err")),
),
);
}
}
ref.watch(userListProvider)
会触发异步加载并返回AsyncValue<List<User>>
;asyncUserList.when(...)
用于根据loading / data / error
不同状态显示不同 UI。
如果需要主动刷新,可以调用
ref.refresh(userListProvider)
或ref.invalidate(userListProvider)
等方法。
7. 总结
- 数据层(Repository + Retrofit):使用
retrofit
+dio
封装所有网络请求; - 模型层(
freezed
+json_serializable
):自动生成不可变数据类,简化序列化/反序列化; - 业务注入(
riverpod_annotation
):通过@riverpod
自动生成各种 Provider(如 Repository Provider、数据 Provider),上层只需ref.watch(...)
即可拿到数据; - UI 层(HookConsumerWidget):使用 Hooks + Riverpod 的方式来管理状态、消费 Provider,轻松地监听数据更新并刷新界面。
通过以上流程,可以把「数据获取逻辑」与「UI/业务」进行良好解耦,并利用 Riverpod 的 Provider 机制,实现可测试、可维护的整体结构。