flutter-clean-arch

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Flutter 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+ Required
Riverpod 3.0+: The
XxxRef
types (like
DioRef
,
UserRepositoryRef
, etc.) have been removed in favor of a unified
Ref
type.
Riverpod 2.x (Legacy):
dart

SomeType someType(SomeTypeRef ref) { ... }
Riverpod 3.x+ (Current):
dart

SomeType someType(Ref ref) { ... }
Freezed 3.0+: Requires
sealed
keyword for union types with Dart 3.3.
Freezed 2.x (Legacy):
dart

class Failure with _$Failure { ... }
Freezed 3.x+ (Current):
dart

sealed 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
等)已被移除,统一使用
Ref
类型。
Riverpod 2.x(旧版)
dart

SomeType someType(SomeTypeRef ref) { ... }
Riverpod 3.x+(当前版本)
dart

SomeType someType(Ref ref) { ... }
Freezed 3.0+:在Dart 3.3中,联合类型需要使用
sealed
关键字。
Freezed 2.x(旧版)
dart

class Failure with _$Failure { ... }
Freezed 3.x+(当前版本)
dart

sealed 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.dart
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.dart

Quick 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
undefined
bash
undefined

Generate 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
undefined
dart run build_runner watch --delete-conflicting-outputs
undefined

Best Practices

最佳实践

DO:
  • Keep domain entities pure (no external dependencies)
  • Use freezed with
    sealed
    keyword for immutable data classes
  • Handle all error cases with Either<Failure, T>
  • Use riverpod_generator with unified
    Ref
    type
  • 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
    XxxRef
    types in new code
推荐做法
  • 保持领域实体纯净(无外部依赖)
  • 结合
    sealed
    关键字使用freezed来创建不可变数据类
  • 使用Either<Failure, T>处理所有错误情况
  • 搭配统一的
    Ref
    类型使用riverpod_generator
  • 将数据层的模型与领域层的实体分离
  • 业务逻辑放在用例中,而非组件里
  • 使用Retrofit进行类型安全的API调用
  • 在仓库中通过NetworkExceptions处理DioException
  • 使用拦截器处理横切关注点(认证、日志)
不推荐做法
  • 在领域层中导入Flutter/HTTP库
  • 将表现层逻辑与业务逻辑混合
  • 使用Either时在组件中直接使用try-catch
  • 创建全能对象或全能提供者
  • 跳过仓库模式
  • 在新代码中使用旧版
    XxxRef
    类型

Common Issues

常见问题

IssueSolution
Build runner conflicts
dart run build_runner clean && dart run build_runner build --delete-conflicting-outputs
Provider not foundEnsure generated files are imported and run build_runner
Either not unwrappingUse
fold()
,
match()
, or
getOrElse()
to extract values
XxxRef
not found
Use unified
Ref
type instead (Riverpod 3.x+)
sealed
keyword error
Upgrade to Dart 3.3+ and Freezed 3.0+
问题解决方案
Build runner冲突
dart run build_runner clean && dart run build_runner build --delete-conflicting-outputs
找不到提供者确保已导入生成的文件并运行build_runner
Either无法解包使用
fold()
match()
getOrElse()
来提取值
找不到
XxxRef
改用统一的
Ref
类型(Riverpod 3.x+)
sealed
关键字错误
升级到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,
    sealed
    modifier)
  • Riverpod 3.0+: State management with unified
    Ref
    type
  • 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请求模式