flutter-apply-architecture-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Architecting Flutter Applications

Flutter应用架构设计

Contents

目录

Architectural Layers

架构分层

Enforce strict Separation of Concerns by dividing the application into distinct layers. Never mix UI rendering with business logic or data fetching.
通过将应用划分为不同层级,严格遵循关注点分离原则。绝不能将UI渲染与业务逻辑或数据获取混在一起。

UI Layer (Presentation)

UI层(展示层)

Implement the MVVM (Model-View-ViewModel) pattern to manage UI state and logic.
  • Views: Write reusable, lean widgets. Restrict logic in Views to UI-specific operations (e.g., animations, layout constraints, simple routing). Pass all required data from the ViewModel.
  • ViewModels: Manage UI state and handle user interactions. Extend
    ChangeNotifier
    (or use
    Listenable
    ) to expose state. Expose immutable state snapshots to the View. Inject Repositories into ViewModels via the constructor.
采用MVVM(Model-View-ViewModel)模式管理UI状态与逻辑。
  • Views(视图): 编写可复用、轻量化的widget。视图中的逻辑仅限UI相关操作(如动画、布局约束、简单路由)。所有所需数据均从ViewModel传入。
  • ViewModels(视图模型): 管理UI状态并处理用户交互。继承
    ChangeNotifier
    (或使用
    Listenable
    )以暴露状态。向视图提供不可变的状态快照。通过构造函数将Repositories注入ViewModel。

Data Layer

数据层

Implement the Repository pattern to isolate data access logic and create a single source of truth.
  • Services: Create stateless classes to wrap external APIs (HTTP clients, local databases, platform plugins). Return raw API models or
    Result
    wrappers.
  • Repositories: Consume one or more Services. Transform raw API models into clean Domain Models. Handle caching, offline synchronization, and retry logic. Expose Domain Models to ViewModels.
采用Repository模式隔离数据访问逻辑,创建单一数据源。
  • Services(服务): 创建无状态类封装外部API(HTTP客户端、本地数据库、平台插件)。返回原始API模型或
    Result
    包装器。
  • Repositories(仓库): 调用一个或多个Services。将原始API模型转换为清晰的领域模型。处理缓存、离线同步与重试逻辑。向ViewModels暴露领域模型。

Logic Layer (Domain - Optional)

逻辑层(领域层 - 可选)

  • Use Cases: Implement this layer only if the application contains complex business logic that clutters the ViewModel, or if logic must be reused across multiple ViewModels. Extract this logic into dedicated Use Case (interactor) classes that sit between ViewModels and Repositories.
  • Use Cases(用例): 仅当应用包含复杂业务逻辑导致ViewModel臃肿,或逻辑需在多个ViewModel间复用时,才实现该层。将此类逻辑提取到独立的用例(交互器)类中,置于ViewModels与Repositories之间。

Project Structure

项目结构

Organize the codebase using a hybrid approach: group UI components by feature, and group Data/Domain components by type.
text
lib/
├── data/
│   ├── models/         # API models
│   ├── repositories/   # Repository implementations
│   └── services/       # API clients, local storage wrappers
├── domain/
│   ├── models/         # Clean domain models
│   └── use_cases/      # Optional business logic classes
└── ui/
    ├── core/           # Shared widgets, themes, typography
    └── features/
        └── [feature_name]/
            ├── view_models/
            └── views/
采用混合方式组织代码库:按功能分组UI组件,按类型分组数据/领域组件。
text
lib/
├── data/
│   ├── models/         # API模型
│   ├── repositories/   # 仓库实现
│   └── services/       # API客户端、本地存储封装
├── domain/
│   ├── models/         # 清晰的领域模型
│   └── use_cases/      # 可选的业务逻辑类
└── ui/
    ├── core/           # 共享widget、主题、排版
    └── features/
        └── [feature_name]/
            ├── view_models/
            └── views/

Workflow: Implementing a New Feature

工作流程:实现新功能

Follow this sequential workflow when adding a new feature to the application. Copy the checklist to track progress.
添加新功能时遵循以下顺序流程。可复制此清单跟踪进度。

Task Progress

任务进度

  • Step 1: Define Domain Models. Create immutable data classes for the feature using
    freezed
    or
    built_value
    .
  • Step 2: Implement Services. Create or update Service classes to handle external API communication.
  • Step 3: Implement Repositories. Create the Repository to consume Services and return Domain Models.
  • Step 4: Apply Conditional Logic (Domain Layer).
    • If the feature requires complex data transformation or cross-repository logic: Create a Use Case class.
    • If the feature is a simple CRUD operation: Skip to Step 5.
  • Step 5: Implement the ViewModel. Create the ViewModel extending
    ChangeNotifier
    . Inject required Repositories/Use Cases. Expose immutable state and command methods.
  • Step 6: Implement the View. Create the UI widget. Use
    ListenableBuilder
    or
    AnimatedBuilder
    to listen to ViewModel changes.
  • Step 7: Inject Dependencies. Register the new Service, Repository, and ViewModel in the dependency injection container (e.g.,
    provider
    or
    get_it
    ).
  • Step 8: Run Validator. Execute unit tests for the ViewModel and Repository.
    • Feedback Loop: Run tests -> Review failures -> Fix logic -> Re-run until passing.
  • 步骤1:定义领域模型。 使用
    freezed
    built_value
    为功能创建不可变数据类。
  • 步骤2:实现Services。 创建或更新Service类以处理外部API通信。
  • 步骤3:实现Repositories。 创建仓库调用Services并返回领域模型。
  • 步骤4:应用条件逻辑(领域层)。
    • 如果功能需要复杂的数据转换或跨仓库逻辑: 创建用例类。
    • 如果功能是简单的CRUD操作: 跳至步骤5。
  • 步骤5:实现ViewModel。 创建继承
    ChangeNotifier
    的ViewModel。注入所需的Repositories/用例。暴露不可变状态与命令方法。
  • 步骤6:实现View。 创建UI widget。使用
    ListenableBuilder
    AnimatedBuilder
    监听ViewModel变化。
  • 步骤7:注入依赖。 在依赖注入容器(如
    provider
    get_it
    )中注册新的Service、Repository与ViewModel。
  • 步骤8:运行验证器。 执行ViewModel与Repository的单元测试。
    • 反馈循环: 运行测试 -> 查看失败项 -> 修复逻辑 -> 重新运行直至通过。

Examples

示例

Data Layer: Service and Repository

数据层:Service与Repository

dart
// 1. Service (Raw API interaction)
class ApiClient {
  Future<UserApiModel> fetchUser(String id) async {
    // HTTP GET implementation...
  }
}

// 2. Repository (Single source of truth, returns Domain Model)
class UserRepository {
  UserRepository({required ApiClient apiClient}) : _apiClient = apiClient;
  
  final ApiClient _apiClient;
  User? _cachedUser;

  Future<User> getUser(String id) async {
    if (_cachedUser != null) return _cachedUser!;
    
    final apiModel = await _apiClient.fetchUser(id);
    _cachedUser = User(id: apiModel.id, name: apiModel.fullName); // Transform to Domain Model
    return _cachedUser!;
  }
}
dart
// 1. Service(原始API交互)
class ApiClient {
  Future<UserApiModel> fetchUser(String id) async {
    // HTTP GET实现...
  }
}

// 2. Repository(单一数据源,返回领域模型)
class UserRepository {
  UserRepository({required ApiClient apiClient}) : _apiClient = apiClient;
  
  final ApiClient _apiClient;
  User? _cachedUser;

  Future<User> getUser(String id) async {
    if (_cachedUser != null) return _cachedUser!;
    
    final apiModel = await _apiClient.fetchUser(id);
    _cachedUser = User(id: apiModel.id, name: apiModel.fullName); // 转换为领域模型
    return _cachedUser!;
  }
}

UI Layer: ViewModel and View

UI层:ViewModel与View

dart
// 3. ViewModel (State management and presentation logic)
class ProfileViewModel extends ChangeNotifier {
  ProfileViewModel({required UserRepository userRepository}) 
      : _userRepository = userRepository;

  final UserRepository _userRepository;

  User? _user;
  User? get user => _user;

  bool _isLoading = false;
  bool get isLoading => _isLoading;

  Future<void> loadProfile(String id) async {
    _isLoading = true;
    notifyListeners();

    try {
      _user = await _userRepository.getUser(id);
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }
}

// 4. View (Dumb UI component)
class ProfileView extends StatelessWidget {
  const ProfileView({super.key, required this.viewModel});

  final ProfileViewModel viewModel;

  
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: viewModel,
      builder: (context, _) {
        if (viewModel.isLoading) {
          return const Center(child: CircularProgressIndicator());
        }
        
        final user = viewModel.user;
        if (user == null) {
          return const Center(child: Text('User not found'));
        }

        return Column(
          children: [
            Text(user.name),
            ElevatedButton(
              onPressed: () => viewModel.loadProfile(user.id),
              child: const Text('Refresh'),
            ),
          ],
        );
      },
    );
  }
}
dart
// 3. ViewModel(状态管理与展示逻辑)
class ProfileViewModel extends ChangeNotifier {
  ProfileViewModel({required UserRepository userRepository}) 
      : _userRepository = userRepository;

  final UserRepository _userRepository;

  User? _user;
  User? get user => _user;

  bool _isLoading = false;
  bool get isLoading => _isLoading;

  Future<void> loadProfile(String id) async {
    _isLoading = true;
    notifyListeners();

    try {
      _user = await _userRepository.getUser(id);
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }
}

// 4. View(纯UI组件)
class ProfileView extends StatelessWidget {
  const ProfileView({super.key, required this.viewModel});

  final ProfileViewModel viewModel;

  
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: viewModel,
      builder: (context, _) {
        if (viewModel.isLoading) {
          return const Center(child: CircularProgressIndicator());
        }
        
        final user = viewModel.user;
        if (user == null) {
          return const Center(child: Text('User not found'));
        }

        return Column(
          children: [
            Text(user.name),
            ElevatedButton(
              onPressed: () => viewModel.loadProfile(user.id),
              child: const Text('Refresh'),
            ),
          ],
        );
      },
    );
  }
}