Flutter 高级架构实践:Clean Architecture + Riverpod + Dio 打造可维护企业级应用
摘要
随着 Flutter 项目规模扩大,代码混乱、测试困难、耦合严重等问题日益凸显。本文将介绍如何基于 Clean Architecture 分层思想,结合 Riverpod 状态管理与 Dio 网络库,构建一个高内聚、低耦合、易测试的企业级应用架构。我们将以"用户信息查询"功能为例,完整实现从 API 调用到 UI 展示的全流程。
一:1. 项目背景与问题分析
在实际开发中,我们经常遇到以下典型问题:
- 代码混乱:业务逻辑、UI 渲染、数据访问代码混杂在一起,形成"面条式"代码
- 测试困难:直接依赖网络请求和数据库等外部资源,单元测试难以开展
- 耦合严重:UI 层直接调用数据层,修改 API 接口会导致连锁反应
以用户信息查询功能为例,传统实现方式可能存在:
// 典型问题代码示例
class UserPage extends StatefulWidget {
@override
_UserPageState createState() => _UserPageState();
}
class _UserPageState extends State<UserPage> {
User? user;
Future<void> fetchUser() async {
final response = await Dio().get('https://api.example.com/users/1');
setState(() {
user = User.fromJson(response.data);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: user != null
? Text(user!.name)
: CircularProgressIndicator(),
),
);
}
}
2. Clean Architecture 分层方案
我们将采用三层架构设计:
2.1 领域层 (Domain Layer)
- 包含核心业务逻辑和实体模型
- 定义业务用例(UseCase)接口
- 完全独立于框架和外部依赖
示例实体模型:
class User {
final String id;
final String name;
final String email;
User({
required this.id,
required this.name,
required this.email,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
}
2.2 数据层 (Data Layer)
- 实现领域层定义的接口
- 包含数据源(本地/远程)实现
- 使用 Repository 模式统一数据访问
数据源接口定义:
abstract class UserRepository {
Future<User> getUser(String userId);
}
2.3 表现层 (Presentation Layer)
- 处理UI展示和用户交互
- 使用状态管理(Riverpod)连接业务逻辑
- 保持UI组件纯净无业务逻辑
3. 完整实现流程
3.1 领域层实现
定义获取用户信息的用例:
class GetUserUseCase {
final UserRepository repository;
GetUserUseCase(this.repository);
Future<User> execute(String userId) {
return repository.getUser(userId);
}
}
3.2 数据层实现
使用Dio实现远程数据源:
class RemoteUserDataSource {
final Dio dio;
RemoteUserDataSource(this.dio);
Future<User> getUser(String userId) async {
final response = await dio.get('/users/$userId');
return User.fromJson(response.data);
}
}
实现Repository:
class UserRepositoryImpl implements UserRepository {
final RemoteUserDataSource remoteDataSource;
UserRepositoryImpl(this.remoteDataSource);
@override
Future<User> getUser(String userId) {
return remoteDataSource.getUser(userId);
}
}
3.3 表现层实现
使用Riverpod管理状态:
final userProvider = FutureProvider.autoDispose.family<User, String>((ref, userId) async {
final repository = ref.read(userRepositoryProvider);
return await repository.getUser(userId);
});
纯净的UI组件:
class UserPage extends ConsumerWidget {
final String userId;
const UserPage({required this.userId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProvider(userId));
return Scaffold(
body: Center(
child: userAsync.when(
loading: () => CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
data: (user) => Text(user.name),
),
),
);
}
}
4. 架构优势
- 可测试性:可以轻松mock各层接口进行单元测试
- 可维护性:修改数据源不会影响业务逻辑和UI层
- 可扩展性:新增功能只需实现对应接口,不影响现有代码
- 团队协作:清晰的分层便于多人并行开发
测试用例示例:
void main() {
test('GetUserUseCase should return user', () async {
// 准备mock repository
final mockRepo = MockUserRepository();
when(mockRepo.getUser('1')).thenAnswer((_) async => User(id: '1', name: 'Test', email: 'test@example.com'));
// 执行用例
final useCase = GetUserUseCase(mockRepo);
final user = await useCase.execute('1');
// 验证结果
expect(user.name, 'Test');
verify(mockRepo.getUser('1')).called(1);
});
}
通过这种架构设计,我们实现了业务逻辑与框架解耦、UI与数据分离、各层职责分明的企业级应用架构。
二:1. 为什么需要 Clean Architecture?
传统 Flutter 项目常把网络请求、业务逻辑、UI 混杂在一起:
1// ❌ 反面示例
2FutureBuilder(
3 future: http.get(Uri.parse('https://api.example.com/user/1')),
4 builder: (context, snapshot) {
5 if (snapshot.hasData) {
6 final user = jsonDecode(snapshot.data!.body);
7 return Text(user['name']);
8 }
9 return CircularProgressIndicator();
10 }
11)
问题:
- 无法单元测试;
- 业务逻辑散落在 UI 中;
- 更换网络库需重写大量代码。
Clean Architecture 通过分层解耦,解决上述问题。
2. Clean Architecture 分层设计
我们将项目分为四层(从内到外):
-
Domain 层(领域层)
- 包含实体(Entity)、用例(UseCase)、仓库接口(Repository Interface)
- 完全不依赖外部库,纯 Dart
-
Data 层(数据层)
- 实现 Domain 层定义的仓库接口
- 依赖 Dio、SharedPreferences 等外部库
- 包含数据模型(Model)、数据源(DataSource)
-
Presentation 层(表现层)
- UI(Widget) + 状态管理(Riverpod)
- 依赖 Domain 层的 UseCase
-
Main 层(依赖注入)
配置依赖关系(如 Dio 实例、Repository 实现)
✅ 依赖规则:外层可依赖内层,内层绝不依赖外层。
3. 项目结构
1lib/
2├── core/
3│ └── error/exception.dart
4│ └── network/network_info.dart
5├── features/
6│ └── user/
7│ ├── data/
8│ │ ├── datasources/user_remote_data_source.dart
9│ │ ├── models/user_model.dart
10│ │ └── repositories/user_repository_impl.dart
11│ ├── domain/
12│ │ ├── entities/user.dart
13│ │ ├── repositories/user_repository.dart
14│ │ └── usecases/get_user.dart
15│ └── presentation/
16│ ├── providers/user_provider.dart
17│ └── widgets/user_widget.dart
18├── main.dart
19└── di/injection.config.dart (使用 get_it + injectable)
为简化,本文使用手动依赖注入,实际项目推荐
get_it+injectable。
4. Domain 层实现
4.1 实体(Entity)
1// lib/features/user/domain/entities/user.dart
2class User {
3 final int id;
4 final String name;
5 final String email;
6
7 User({required this.id, required this.name, required this.email});
8
9 factory User.fromJson(Map<String, dynamic> json) {
10 return User(
11 id: json['id'],
12 name: json['name'],
13 email: json['email'],
14 );
15 }
16}
4.2 仓库接口
1// lib/features/user/domain/repositories/user_repository.dart
2abstract class UserRepository {
3 Future<User> getUser(int id);
4}
4.3 用例(UseCase)
1// lib/features/user/domain/usecases/get_user.dart
2import 'package:dartz/dartz.dart'; // 推荐使用 dartz 处理 Either<Failure, Success>
3import '../repositories/user_repository.dart';
4import '../entities/user.dart';
5
6class GetUser {
7 final UserRepository repository;
8
9 GetUser(this.repository);
10
11 Future<Either<Failure, User>> call(int id) async {
12 try {
13 final user = await repository.getUser(id);
14 return Right(user);
15 } on ServerException {
16 return Left(ServerFailure());
17 }
18 }
19}
20
21// 错误类型
22class ServerFailure implements Failure {}
23abstract class Failure {}
5. Data 层实现
5.1 数据模型(Model)
1// lib/features/user/data/models/user_model.dart
2import 'package:json_annotation/json_annotation.dart';
3import '../../domain/entities/user.dart';
4
5part 'user_model.g.dart';
6
7@JsonSerializable()
8class UserModel extends User {
9 UserModel({
10 required super.id,
11 required super.name,
12 required super.email,
13 });
14
15 factory UserModel.fromJson(Map<String, dynamic> json) =>
16 _$UserModelFromJson(json);
17
18 Map<String, dynamic> toJson() => _$UserModelToJson(this);
19}
需添加
build_runner生成user_model.g.dart。
5.2 远程数据源
1// lib/features/user/data/datasources/user_remote_data_source.dart
2import 'package:dio/dio.dart';
3import '../../../../core/error/exception.dart';
4
5abstract class UserRemoteDataSource {
6 Future<Map<String, dynamic>> getUser(int id);
7}
8
9class UserRemoteDataSourceImpl implements UserRemoteDataSource {
10 final Dio dio;
11
12 UserRemoteDataSourceImpl(this.dio);
13
14 @override
15 Future<Map<String, dynamic>> getUser(int id) async {
16 try {
17 final response = await dio.get('/users/$id');
18 return response.data;
19 } on DioException catch (e) {
20 throw ServerException(message: e.message ?? 'Unknown error');
21 }
22 }
23}
5.3 仓库实现
1// lib/features/user/data/repositories/user_repository_impl.dart
2import '../../domain/entities/user.dart';
3import '../../domain/repositories/user_repository.dart';
4import '../datasources/user_remote_data_source.dart';
5import '../models/user_model.dart';
6
7class UserRepositoryImpl implements UserRepository {
8 final UserRemoteDataSource remoteDataSource;
9
10 UserRepositoryImpl(this.remoteDataSource);
11
12 @override
13 Future<User> getUser(int id) async {
14 final jsonData = await remoteDataSource.getUser(id);
15 final userModel = UserModel.fromJson(jsonData);
16 return userModel;
17 }
18}
6. Presentation 层:Riverpod 状态管理
6.1 Provider 定义
1// lib/features/user/presentation/providers/user_provider.dart
2import 'package:flutter_riverpod/flutter_riverpod.dart';
3import '../../domain/usecases/get_user.dart';
4
5final userProvider = FutureProvider.autoDispose.family<User, int>((ref, userId) async {
6 final getUser = ref.watch(getUserUseCaseProvider);
7 final result = await getUser(userId);
8 return result.fold(
9 (failure) => throw Exception('Failed to load user'),
10 (user) => user,
11 );
12});
13
14// 注入 UseCase
15final getUserUseCaseProvider = Provider((ref) {
16 // 此处应从 DI 容器获取,简化起见直接 new
17 final remoteDataSource = UserRemoteDataSourceImpl(Dio());
18 final repository = UserRepositoryImpl(remoteDataSource);
19 return GetUser(repository);
20});
6.2 UI 组件
1// lib/features/user/presentation/widgets/user_widget.dart
2import 'package:flutter_riverpod/flutter_riverpod.dart';
3import '../providers/user_provider.dart';
4
5class UserWidget extends ConsumerWidget {
6 final int userId;
7
8 const UserWidget({required this.userId, super.key});
9
10 @override
11 Widget build(BuildContext context, WidgetRef ref) {
12 final userAsync = ref.watch(userProvider(userId));
13
14 return userAsync.when(
15 loading: () => const Center(child: CircularProgressIndicator()),
16 error: (err, stack) => Center(child: Text('Error: $err')),
17 data: (user) => Column(
18 children: [
19 Text('Name: ${user.name}'),
20 Text('Email: ${user.email}'),
21 ],
22 ),
23 );
24 }
25}
7. 依赖注入与主入口
1// lib/main.dart
2void main() {
3 // 配置 Dio
4 final dio = Dio(BaseOptions(baseUrl: 'https://jsonplaceholder.typicode.com'));
5
6 runApp(
7 ProviderScope(
8 overrides: [
9 getUserUseCaseProvider.overrideWith(() {
10 final remoteDataSource = UserRemoteDataSourceImpl(dio);
11 final repository = UserRepositoryImpl(remoteDataSource);
12 return GetUser(repository);
13 }),
14 ],
15 child: const MyApp(),
16 ),
17 );
18}
19
20class MyApp extends StatelessWidget {
21 const MyApp({super.key});
22
23 @override
24 Widget build(BuildContext context) {
25 return MaterialApp(
26 home: Scaffold(
27 appBar: AppBar(title: Text('Clean Architecture Demo')),
28 body: UserWidget(userId: 1),
29 ),
30 );
31 }
32}
8. 单元测试示例
得益于分层,我们可以轻松测试 UseCase:
1// test/features/user/domain/usecases/get_user_test.dart
2import 'package:mockito/mockito.dart';
3import 'package:test/test.dart';
4import '../../../../lib/features/user/domain/usecases/get_user.dart';
5import '../../../../lib/features/user/domain/repositories/user_repository.dart';
6
7class MockUserRepository extends Mock implements UserRepository {}
8
9void main() {
10 late GetUser getUser;
11 late MockUserRepository mockRepository;
12
13 setUp(() {
14 mockRepository = MockUserRepository();
15 getUser = GetUser(mockRepository);
16 });
17
18 test('should get user from repository', () async {
19 // arrange
20 final tUser = User(id: 1, name: 'John', email: 'john@example.com');
21 when(mockRepository.getUser(any)).thenAnswer((_) async => tUser);
22
23 // act
24 final result = await getUser(1);
25
26 // assert
27 expect(result, Right(tUser));
28 verify(mockRepository.getUser(1));
29 });
30}
9. 小结
本文通过 Clean Architecture + Riverpod + Dio 的组合,构建了一个可扩展、可测试、易维护的 Flutter 应用架构。关键收益包括:
- 关注点分离:业务逻辑与 UI、数据源完全解耦;
- 易于测试:Domain 层可独立单元测试;
- 灵活替换:更换网络库只需修改 Data 层;
- 团队协作友好:各层职责清晰,减少冲突。
📌 建议:小型项目可简化分层,但中大型项目务必引入架构约束,否则后期维护成本极高。
💬 互动提问:你在 Flutter 网络请求中遇到过哪些坑?欢迎评论区交流!
❤️ 如果本文对你有帮助,请点赞、收藏、转发支持原创!
更多推荐



所有评论(0)