flutter-clean-arch
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFlutter Clean Architecture Skill
Flutter Clean Architecture 技能
Generate Flutter applications following Clean Architecture principles with feature-first organization, Riverpod for state management, and functional error handling using fpdart.
Includes Dio + Retrofit for type-safe REST API calls.
遵循Clean Architecture(整洁架构)原则,以优先按功能划分的组织方式、Riverpod状态管理以及fpdart函数式错误处理来生成Flutter应用。
包含用于类型安全REST API调用的Dio + Retrofit。
Core Principles
核心原则
Architecture: Clean Architecture (Feature-First)
- Domain layer: Pure business logic, no dependencies
- Data layer: Data sources, repositories implementation, data models
- Presentation layer: UI, state management, view models
Dependency Rule: Presentation → Domain ← Data (Domain has no external dependencies)
State Management: Riverpod 3.0+ with code generation
Note: Riverpod 3.0+ & Freezed 3.0+ RequiredRiverpod 3.0+: Thetypes (likeXxxRef,DioRef, etc.) have been removed in favor of a unifiedUserRepositoryReftype.RefRiverpod 2.x (Legacy):dartSomeType someType(SomeTypeRef ref) { ... }Riverpod 3.x+ (Current):dartSomeType someType(Ref ref) { ... }Freezed 3.0+: Requireskeyword for union types with Dart 3.3.sealedFreezed 2.x (Legacy):dartclass Failure with _$Failure { ... }Freezed 3.x+ (Current):dartsealed class Failure with _$Failure { ... }Required versions: This skill requires Riverpod 3.0+ and Freezed 3.0+. Check your version with.flutter pub deps | grep riverpod
Error Handling: fpdart's Either<Failure, T> for functional error handling
Networking: Dio + Retrofit for type-safe REST API calls
架构:Clean Architecture(整洁架构)(优先按功能划分)
- 领域层:纯业务逻辑,无外部依赖
- 数据层:数据源、仓库实现、数据模型
- 表现层:UI、状态管理、视图模型
依赖规则:表现层 → 领域层 ← 数据层(领域层无外部依赖)
状态管理:Riverpod 3.0+ 搭配代码生成
注意:需要 Riverpod 3.0+ 和 Freezed 3.0+Riverpod 3.0+:类型(如XxxRef、DioRef等)已被移除,统一使用UserRepositoryRef类型。RefRiverpod 2.x(旧版):dartSomeType someType(SomeTypeRef ref) { ... }Riverpod 3.x+(当前版本):dartSomeType someType(Ref ref) { ... }Freezed 3.0+:在Dart 3.3中,联合类型需要使用关键字。sealedFreezed 2.x(旧版):dartclass Failure with _$Failure { ... }Freezed 3.x+(当前版本):dartsealed class Failure with _$Failure { ... }所需版本:此技能需要Riverpod 3.0+和Freezed 3.0+。可以通过检查你的版本。flutter pub deps | grep riverpod
错误处理:使用fpdart的Either<Failure, T>进行函数式错误处理
网络请求:使用Dio + Retrofit进行类型安全的REST API调用
Project Structure
项目结构
lib/
├── core/
│ ├── constants/
│ │ ├── api_constants.dart
│ ├── errors/
│ │ ├── failures.dart
│ │ └── network_exceptions.dart
│ ├── network/
│ │ ├── dio_provider.dart
│ │ └── interceptors/
│ │ ├── auth_interceptor.dart
│ │ ├── logging_interceptor.dart
│ │ └── error_interceptor.dart
│ ├── storage/
│ ├── services/
│ ├── router/
│ │ └── app_router.dart
│ └── utils/
├── shared/
├── features/
│ └── [feature_name]/
│ ├── data/
│ │ ├── models/
│ │ │ └── [entity]_model.dart
│ │ ├── datasources/
│ │ │ └── [feature]_api_service.dart
│ │ └── repositories/
│ │ └── [feature]_repository_impl.dart
│ ├── domain/
│ │ ├── entities/
│ │ ├── repositories/
│ │ │ └── [feature]_repository.dart
│ │ └── usecases/
│ │ └── [action]_usecase.dart
│ └── presentation/
│ ├── providers/
│ │ └── [feature]_provider.dart
│ ├── screens/
│ │ └── [feature]_screen.dart
│ └── widgets/
│ └── [feature]_widget.dart
└── main.dartlib/
├── core/
│ ├── constants/
│ │ ├── api_constants.dart
│ ├── errors/
│ │ ├── failures.dart
│ │ └── network_exceptions.dart
│ ├── network/
│ │ ├── dio_provider.dart
│ │ └── interceptors/
│ │ ├── auth_interceptor.dart
│ │ ├── logging_interceptor.dart
│ │ └── error_interceptor.dart
│ ├── storage/
│ ├── services/
│ ├── router/
│ │ └── app_router.dart
│ └── utils/
├── shared/
├── features/
│ └── [feature_name]/
│ ├── data/
│ │ ├── models/
│ │ │ └── [entity]_model.dart
│ │ ├── datasources/
│ │ │ └── [feature]_api_service.dart
│ │ └── repositories/
│ │ └── [feature]_repository_impl.dart
│ ├── domain/
│ │ ├── entities/
│ │ ├── repositories/
│ │ │ └── [feature]_repository.dart
│ │ └── usecases/
│ │ └── [action]_usecase.dart
│ └── presentation/
│ ├── providers/
│ │ └── [feature]_provider.dart
│ ├── screens/
│ │ └── [feature]_screen.dart
│ └── widgets/
│ └── [feature]_widget.dart
└── main.dartQuick Start
快速开始
1. Domain Layer (Entities, Repository Interfaces, UseCases)
1. 领域层(实体、仓库接口、用例)
dart
// Entity
sealed class User with _$User {
const factory User({
required String id,
required String name,
required String email,
}) = _User;
}
// Repository Interface
abstract class UserRepository {
Future<Either<Failure, User>> getUser(String id);
}
// UseCase
class GetUser {
final UserRepository repository;
GetUser(this.repository);
Future<Either<Failure, User>> call(String id) => repository.getUser(id);
}dart
// Entity
sealed class User with _$User {
const factory User({
required String id,
required String name,
required String email,
}) = _User;
}
// Repository Interface
abstract class UserRepository {
Future<Either<Failure, User>> getUser(String id);
}
// UseCase
class GetUser {
final UserRepository repository;
GetUser(this.repository);
Future<Either<Failure, User>> call(String id) => repository.getUser(id);
}2. Data Layer (Models, API Service, Repository Implementation)
2. 数据层(模型、API服务、仓库实现)
dart
// Model with JSON serialization
sealed class UserModel with _$UserModel {
const UserModel._();
const factory UserModel({
required String id,
required String name,
required String email,
}) = _UserModel;
factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json);
User toEntity() => User(id: id, name: name, email: email);
}
// Retrofit API Service
()
abstract class UserApiService {
factory UserApiService(Dio dio) = _UserApiService;
('/users/{id}')
Future<UserModel> getUser(('id') String id);
}
// Repository Implementation
class UserRepositoryImpl implements UserRepository {
final UserApiService apiService;
Future<Either<Failure, User>> getUser(String id) async {
try {
final userModel = await apiService.getUser(id);
return Right(userModel.toEntity());
} on DioException catch (e) {
return Left(Failure.network(NetworkExceptions.fromDioError(e).message));
}
}
}dart
// Model with JSON serialization
sealed class UserModel with _$UserModel {
const UserModel._();
const factory UserModel({
required String id,
required String name,
required String email,
}) = _UserModel;
factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json);
User toEntity() => User(id: id, name: name, email: email);
}
// Retrofit API Service
()
abstract class UserApiService {
factory UserApiService(Dio dio) = _UserApiService;
('/users/{id}')
Future<UserModel> getUser(('id') String id);
}
// Repository Implementation
class UserRepositoryImpl implements UserRepository {
final UserApiService apiService;
Future<Either<Failure, User>> getUser(String id) async {
try {
final userModel = await apiService.getUser(id);
return Right(userModel.toEntity());
} on DioException catch (e) {
return Left(Failure.network(NetworkExceptions.fromDioError(e).message));
}
}
}3. Presentation Layer (Providers, Screens)
3. 表现层(提供者、页面)
dart
// Provider
UserApiService userApiService(Ref ref) {
return UserApiService(ref.watch(dioProvider));
}
UserRepositoryImpl userRepository(Ref ref) {
return UserRepositoryImpl(ref.watch(userApiServiceProvider));
}
class UserNotifier extends _$UserNotifier {
FutureOr<User?> build() => null;
Future<void> fetchUser(String id) async {
state = const AsyncLoading();
final result = await ref.read(userRepositoryProvider).getUser(id);
state = result.fold(
(failure) => AsyncError(failure, StackTrace.current),
(user) => AsyncData(user),
);
}
}
// Screen
class UserScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final userState = ref.watch(userNotifierProvider);
return Scaffold(
body: userState.when(
data: (user) => Text('Hello ${user?.name}'),
loading: () => const CircularProgressIndicator(),
error: (e, _) => Text('Error: $e'),
),
);
}
}dart
// Provider
UserApiService userApiService(Ref ref) {
return UserApiService(ref.watch(dioProvider));
}
UserRepositoryImpl userRepository(Ref ref) {
return UserRepositoryImpl(ref.watch(userApiServiceProvider));
}
class UserNotifier extends _$UserNotifier {
FutureOr<User?> build() => null;
Future<void> fetchUser(String id) async {
state = const AsyncLoading();
final result = await ref.read(userRepositoryProvider).getUser(id);
state = result.fold(
(failure) => AsyncError(failure, StackTrace.current),
(user) => AsyncData(user),
);
}
}
// Screen
class UserScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final userState = ref.watch(userNotifierProvider);
return Scaffold(
body: userState.when(
data: (user) => Text('Hello ${user?.name}'),
loading: () => const CircularProgressIndicator(),
error: (e, _) => Text('Error: $e'),
),
);
}
}Code Generation
代码生成
bash
undefinedbash
undefinedGenerate all files
生成所有文件
dart run build_runner build --delete-conflicting-outputs
dart run build_runner build --delete-conflicting-outputs
Watch mode
监听模式
dart run build_runner watch --delete-conflicting-outputs
undefineddart run build_runner watch --delete-conflicting-outputs
undefinedBest Practices
最佳实践
DO:
- Keep domain entities pure (no external dependencies)
- Use freezed with keyword for immutable data classes
sealed - Handle all error cases with Either<Failure, T>
- Use riverpod_generator with unified type
Ref - Separate models (data) from entities (domain)
- Place business logic in use cases, not in widgets
- Use Retrofit for type-safe API calls
- Handle DioException in repositories with NetworkExceptions
- Use interceptors for cross-cutting concerns (auth, logging)
DON'T:
- Import Flutter/HTTP libraries in domain layer
- Mix presentation logic with business logic
- Use try-catch directly in widgets when using Either
- Create god objects or god providers
- Skip the repository pattern
- Use legacy types in new code
XxxRef
推荐做法:
- 保持领域实体纯净(无外部依赖)
- 结合关键字使用freezed来创建不可变数据类
sealed - 使用Either<Failure, T>处理所有错误情况
- 搭配统一的类型使用riverpod_generator
Ref - 将数据层的模型与领域层的实体分离
- 业务逻辑放在用例中,而非组件里
- 使用Retrofit进行类型安全的API调用
- 在仓库中通过NetworkExceptions处理DioException
- 使用拦截器处理横切关注点(认证、日志)
不推荐做法:
- 在领域层中导入Flutter/HTTP库
- 将表现层逻辑与业务逻辑混合
- 使用Either时在组件中直接使用try-catch
- 创建全能对象或全能提供者
- 跳过仓库模式
- 在新代码中使用旧版类型
XxxRef
Common Issues
常见问题
| Issue | Solution |
|---|---|
| Build runner conflicts | |
| Provider not found | Ensure generated files are imported and run build_runner |
| Either not unwrapping | Use |
| Use unified |
| Upgrade to Dart 3.3+ and Freezed 3.0+ |
| 问题 | 解决方案 |
|---|---|
| Build runner冲突 | |
| 找不到提供者 | 确保已导入生成的文件并运行build_runner |
| Either无法解包 | 使用 |
找不到 | 改用统一的 |
| 升级到Dart 3.3+和Freezed 3.0+ |
Knowledge References
参考库
Primary Libraries (used in this skill):
- Flutter 3.19+: Latest framework features
- Dart 3.3+: Language features (patterns, records, modifier)
sealed - Riverpod 3.0+: State management with unified type
Ref - Dio 5.9+: HTTP client with interceptors
- Retrofit 4.9+: Type-safe REST API code generation
- freezed 3.0+: Immutable data classes with code generation
- json_serializable 6.x: JSON serialization
- go_router 14.x+: Declarative routing
- fpdart: Functional error handling with Either type
主要使用的库(本技能中使用):
- Flutter 3.19+:最新框架特性
- Dart 3.3+:语言特性(模式、记录、修饰符)
sealed - Riverpod 3.0+:统一类型的状态管理库
Ref - Dio 5.9+:带拦截器的HTTP客户端
- Retrofit 4.9+:类型安全REST API代码生成工具
- freezed 3.0+:带代码生成的不可变数据类库
- json_serializable 6.x:JSON序列化工具
- go_router 14.x+:声明式路由库
- fpdart:带Either类型的函数式错误处理库
References
参考文档
- quick_start.md - Step-by-step feature creation workflow
- data_layer.md - Models, Retrofit API services, Repositories
- presentation_layer.md - Providers, Screens, Widgets patterns
- network_setup.md - Dio provider, Interceptors, Network exceptions
- error_handling.md - Either patterns, Failure types, Error strategies
- retrofit_patterns.md - Complete Retrofit API request patterns
- quick_start.md - 分步功能创建流程
- data_layer.md - 模型、Retrofit API服务、仓库
- presentation_layer.md - 提供者、页面、组件模式
- network_setup.md - Dio提供者、拦截器、网络异常
- error_handling.md - Either模式、Failure类型、错误策略
- retrofit_patterns.md - 完整Retrofit API请求模式