摘要

随着 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. 架构优势

  1. 可测试性:可以轻松mock各层接口进行单元测试
  2. 可维护性:修改数据源不会影响业务逻辑和UI层
  3. 可扩展性:新增功能只需实现对应接口,不影响现有代码
  4. 团队协作:清晰的分层便于多人并行开发

测试用例示例:

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 分层设计

我们将项目分为四层(从内到外):

  1. Domain 层(领域层)

    • 包含实体(Entity)、用例(UseCase)、仓库接口(Repository Interface)
    • 完全不依赖外部库,纯 Dart
  2. Data 层(数据层)

    • 实现 Domain 层定义的仓库接口
    • 依赖 Dio、SharedPreferences 等外部库
    • 包含数据模型(Model)、数据源(DataSource)
  3. Presentation 层(表现层)

    • UI(Widget) + 状态管理(Riverpod)
    • 依赖 Domain 层的 UseCase
  4. 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 网络请求中遇到过哪些坑?欢迎评论区交流!
❤️ 如果本文对你有帮助,请点赞、收藏、转发支持原创!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐