mcpField
F
flutter-project-structure

flutter-project-structure

github.com/junsuk5开发2446 次安装24 周前更新
代码仓库

关于 flutter-project-structure

为 Flutter 项目提供单包 Clean Architecture 目录与依赖规则的设计指南,适合希望规范功能、领域和数据层的开发者。它还给出新增功能时的文件布局与注册清单。

主要功能

  1. 01单包分层目录结构
  2. 02领域层纯 Dart 规则
  3. 03功能隔离与共享代码约定
  4. 04新增功能实施清单
  5. 05依赖注入与路由布局

适用场景

  1. 01规划 Flutter 项目的 lib/ 结构
  2. 02为新功能安排模型与用例
  3. 03检查跨层和跨功能依赖
  4. 04统一路由与依赖注册位置

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/junsuk5/survival-flutter-skills --skill flutter-project-structure

安装后在 Claude Code 中输入/flutter-project-structure确认已加载。

详细介绍

Flutter 项目结构(单一包 / Clean Architecture)

核心原则

  • 单一包,分层隔离:不使用类似 Android/KMP 的 Gradle 多模块,而是在 lib/ 下按层划分。
  • Clean Architecture 依赖方向:presentation → domain ← data。domain 是不依赖任何内容的纯 Dart 层。
  • Feature 以文件夹形式置于 presentation 下。 多个屏幕共用的代码放到 core/,仅供单个功能使用的代码则放在对应的 feature 文件夹中。
  • 禁止 feature 之间直接引用。跨功能共享逻辑应放到 core(共享组件、工具)或 domain(模型、用例)中。

目录结构

本项目采用以下结构(以 lib/ 为基准):

lib/
├─ main.dart                          ← 앱 진입점, diSetup() 호출, MaterialApp.router
├─ core/                              ← 전역 공유 인프라
│  ├─ di/di_setup.dart                ← get_it 모듈 등록
│  ├─ routing/router.dart             ← GoRouter 정의
│  ├─ routing/route_paths.dart        ← 경로 상수
│  ├─ domain/error/                   ← Error, Result, NetworkError
│  └─ presentation/
│     ├─ components/                  ← 공용 위젯 (BigButton, SearchInputField 등)
│     └─ dialogs/                     ← 공용 다이얼로그
├─ data/
│  ├─ data_source/                    ← DataSource 인터페이스 + 구현 (local/, remote/)
│  ├─ repository/                     ← Repository 구현체 (*Impl)
│  └─ clipboard/                      ← 기타 플랫폼 서비스 구현
├─ domain/
│  ├─ model/                          ← freezed 도메인 모델
│  ├─ repository/                     ← Repository 인터페이스 (abstract interface class)
│  ├─ use_case/                       ← UseCase 클래스 (execute() 단일 진입점)
│  ├─ clipboard/                      ← 플랫폼 서비스 인터페이스
│  ├─ error/                          ← 기능별 Error enum
│  └─ filter/                         ← 도메인 값 객체
├─ presentation/
│  └─ <feature>/
│     ├─ <feature>_view_model.dart    ← 파일 상단: freezed State·Action, 하단: ChangeNotifier VM
│     └─ <feature>_screen.dart        ← 파일 상단: Root(VM 주입), 하단: Screen(순수 UI)
└─ ui/                                ← 색상/타이포 등 디자인 토큰

实际示例:lib/presentation/ingredient/、lib/presentation/saved_recipes/、lib/presentation/home/。

每个 feature 由 2 个文件组成:

<feature>_view_model.dart 的内部结构

// ① State (freezed)
@freezed
class IngredientState with _$IngredientState {
  const factory IngredientState({ ... }) = _IngredientState;
}

// ② Action (freezed sealed)
@freezed
sealed class IngredientAction with _$IngredientAction {
  const factory IngredientAction.load() = Load;
  const factory IngredientAction.delete(String id) = Delete;
}

// ③ ViewModel
class IngredientViewModel extends ChangeNotifier {
  IngredientState _state = const IngredientState();
  IngredientState get state => _state;

  void onAction(IngredientAction action) { ... }
}

<feature>_screen.dart 的内部结构

// ① Root — getIt으로 VM 주입, ListenableBuilder로 상태 감지
class IngredientRoot extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final vm = getIt<IngredientViewModel>();
    return ListenableBuilder(
      listenable: vm,
      builder: (_, __) => IngredientScreen(
        state: vm.state,
        onAction: vm.onAction,
      ),
    );
  }
}

// ② Screen — 순수 UI, state·onAction만 인자로 받음
class IngredientScreen extends StatelessWidget {
  const IngredientScreen({required this.state, required this.onAction});
  final IngredientState state;
  final void Function(IngredientAction) onAction;

  @override
  Widget build(BuildContext context) { ... }
}

各类代码的存放位置

代码类型位置实际示例
领域模型(freezed)lib/domain/model/recipe.dart、ingredient.dart
Repository 接口lib/domain/repository/recipe_repository.dart
UseCaselib/domain/use_case/get_saved_recipes_use_case.dart
DataSource 接口lib/data/data_source/recipe_data_source.dart
DataSource 实现lib/data/data_source/{local,remote}/remote_recipe_data_source_impl.dart
Repository 实现lib/data/repository/mock_recipe_repository_impl.dart
State + Action + ViewModellib/presentation/<feature>/<feature>_view_model.dartingredient_view_model.dart
Root + Screen 组件lib/presentation/<feature>/<feature>_screen.dartingredient_screen.dart
共享组件lib/core/presentation/components/big_button.dart
路由定义lib/core/routing/router.dart—
DI 注册lib/core/di/di_setup.dart—
共享错误类型lib/core/domain/error/result.dart、network_error.dart
功能专属错误lib/domain/error/bookmark_error.dart、new_recipe_error.dart

依赖规则

层可依赖
presentationdomain、core
datadomain、core
domain其他 domain 文件、core/domain(仅纯 Dart)。绝对禁止依赖 data、presentation、flutter/material
core/presentationflutter/material、core/domain
core/domain仅纯 Dart

domain/ 文件中不得出现 package:flutter/... 导入。保持纯 Dart,以确保可测试性和可复用性。


添加功能时(检查清单)

添加新 feature(例如 reviews)时:

  • lib/domain/model/review.dart — 定义 freezed 模型
  • lib/domain/repository/review_repository.dart — 使用 abstract interface class 定义接口
  • lib/domain/error/review_error.dart — 必要时定义功能专属错误枚举(implements Error)
  • lib/domain/use_case/get_reviews_use_case.dart — 仅包含一个 execute() 的 UseCase
  • lib/data/data_source/remote/remote_review_data_source_impl.dart — 实现
  • lib/data/repository/mock_review_repository_impl.dart 或实际实现
  • lib/presentation/reviews/reviews_view_model.dart — 文件顶部放置 ReviewsState、ReviewsAction,底部放置 ReviewsViewModel
  • lib/presentation/reviews/reviews_screen.dart — 文件顶部放置 ReviewsRoot,底部放置 ReviewsScreen
  • 在 lib/core/di/di_setup.dart 中注册 DataSource、Repository、UseCase、ViewModel
  • 在 lib/core/routing/route_paths.dart 中定义路径常量,并在 router.dart 中添加 GoRoute

遵循此顺序后,每一层只会了解其下层,因此不会出现依赖方向反转。


判断应将代码放入 core 还是 feature

放在 feature 文件夹中的情况

  • 仅供该 feature 使用的 State、Action、ViewModel、UI 模型和屏幕专属组件。
  • 示例:ingredient_state.dart 位于 lib/presentation/ingredient/ 下,不会被其他 feature 使用。

放入 core 的情况

  • 供 2 个或更多 feature 实际使用的组件、工具或错误类型。
  • 示例:BigButton、SearchInputField、Result、NetworkError。
  • 但不要仅仅因为“以后可能会共享”就移入 core。等到出现实际的第二个使用场景时再移动。

핵심 라이브러리

관심사라이브러리용도
DIget_itGetIt.instance 기반 싱글톤/팩토리 등록
라우팅go_router선언적 라우팅, StatefulShellRoute
모델/상태/액션freezed + freezed_annotationsealed/immutable 데이터 클래스
JSONjson_serializable + json_annotationfromJson/toJson 생성
스트림rxdartBehaviorSubject로 반응형 저장소
테스트flutter_test위젯/단위 테스트

모든 코드 생성이 필요한 파일(*.freezed.dart, *.g.dart)은 dart run build_runner build --delete-conflicting-outputs 로 만든다.

评论

支持 Markdown