flutter-architecture

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Flutter App Architecture Implementation

Flutter App Architecture 实现

Goal

目标

Implements a scalable, maintainable Flutter application architecture using the MVVM pattern, unidirectional data flow, and strict separation of concerns across UI, Domain, and Data layers. Assumes a standard Flutter environment utilizing
provider
for dependency injection and
ListenableBuilder
for reactive UI updates.
使用MVVM模式、单向数据流以及UI、领域、数据层之间严格的关注点分离,实现可扩展、可维护的Flutter应用架构。假设标准Flutter环境使用
provider
进行依赖注入,使用
ListenableBuilder
实现响应式UI更新。

Decision Logic

决策逻辑

Before implementing a feature, evaluate the architectural requirements using the following logic:
  1. Data Source:
    • If interacting with an external API -> Create a Remote Service.
    • If interacting with local storage (SQL/Key-Value) -> Create a Local Service.
  2. Business Logic Complexity:
    • If the feature requires merging data from multiple repositories or contains highly complex, reusable logic -> Implement a Domain Layer (UseCases).
    • If the feature is standard CRUD or simple data presentation -> Skip the Domain Layer; the ViewModel communicates directly with the Repository.
在实现功能前,使用以下逻辑评估架构要求:
  1. 数据源:
    • 如果需要与外部API交互 -> 创建远程服务。
    • 如果需要与本地存储(SQL/键值对)交互 -> 创建本地服务。
  2. 业务逻辑复杂度:
    • 如果功能需要合并多个Repository的数据,或包含高度复杂的可复用逻辑 -> 实现领域层(UseCases)。
    • 如果功能是标准CRUD或简单数据展示 -> 跳过领域层,ViewModel直接与Repository通信。

Instructions

使用说明

  1. Analyze Feature Requirements Evaluate the requested feature to determine the necessary data models, services, and UI state. STOP AND ASK THE USER: "Please provide the specific data models, API endpoints, or local storage requirements for this feature, and confirm if complex business logic requires a dedicated Domain (UseCase) layer."
  2. Implement the Data Layer: Services Create a stateless service class to wrap the external API or local storage. This class must not contain business logic or state.
    dart
    class SharedPreferencesService {
      static const String _kDarkMode = 'darkMode';
    
      Future<void> setDarkMode(bool value) async {
        final prefs = await SharedPreferences.getInstance();
        await prefs.setBool(_kDarkMode, value);
      }
    
      Future<bool> isDarkMode() async {
        final prefs = await SharedPreferences.getInstance();
        return prefs.getBool(_kDarkMode) ?? false;
      }
    }
  3. Implement the Data Layer: Repositories Create a repository to act as the single source of truth. The repository consumes the service, handles errors using
    Result
    objects, and exposes domain models or streams.
    dart
    class ThemeRepository {
      ThemeRepository(this._service);
    
      final _darkModeController = StreamController<bool>.broadcast();
      final SharedPreferencesService _service;
    
      Future<Result<bool>> isDarkMode() async {
        try {
          final value = await _service.isDarkMode();
          return Result.ok(value);
        } on Exception catch (e) {
          return Result.error(e);
        }
      }
    
      Future<Result<void>> setDarkMode(bool value) async {
        try {
          await _service.setDarkMode(value);
          _darkModeController.add(value);
          return Result.ok(null);
        } on Exception catch (e) {
          return Result.error(e);
        }
      }
    
      Stream<bool> observeDarkMode() => _darkModeController.stream;
    }
  4. Implement the UI Layer: ViewModels Create a
    ChangeNotifier
    to manage UI state. Use the Command pattern to handle user interactions and asynchronous repository calls.
    dart
    class ThemeSwitchViewModel extends ChangeNotifier {
      ThemeSwitchViewModel(this._themeRepository) {
        load = Command0(_load)..execute();
        toggle = Command0(_toggle);
      }
    
      final ThemeRepository _themeRepository;
      bool _isDarkMode = false;
    
      bool get isDarkMode => _isDarkMode;
    
      late final Command0<void> load;
      late final Command0<void> toggle;
    
      Future<Result<void>> _load() async {
        final result = await _themeRepository.isDarkMode();
        if (result is Ok<bool>) {
          _isDarkMode = result.value;
        }
        notifyListeners();
        return result;
      }
    
      Future<Result<void>> _toggle() async {
        _isDarkMode = !_isDarkMode;
        final result = await _themeRepository.setDarkMode(_isDarkMode);
        notifyListeners();
        return result;
      }
    }
  5. Implement the UI Layer: Views Create a
    StatelessWidget
    that observes the ViewModel using
    ListenableBuilder
    . The View must contain zero business logic.
    dart
    class ThemeSwitch extends StatelessWidget {
      const ThemeSwitch({super.key, required this.viewmodel});
    
      final ThemeSwitchViewModel viewmodel;
    
      
      Widget build(BuildContext context) {
        return Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16.0),
          child: Row(
            children: [
              const Text('Dark Mode'),
              ListenableBuilder(
                listenable: viewmodel,
                builder: (context, _) {
                  return Switch(
                    value: viewmodel.isDarkMode,
                    onChanged: (_) {
                      viewmodel.toggle.execute();
                    },
                  );
                },
              ),
            ],
          ),
        );
      }
    }
  6. Wire Dependencies Inject the dependencies at the application or route level using constructor injection or a dependency injection framework like
    provider
    .
    dart
    void main() {
      runApp(
        MainApp(
          themeRepository: ThemeRepository(SharedPreferencesService()),
        ),
      );
    }
  7. Validate and Fix Review the generated implementation against the constraints. Ensure that data flows strictly downwards (Repository -> ViewModel -> View) and events flow strictly upwards (View -> ViewModel -> Repository). If a View contains data mutation logic, extract it to the ViewModel. If a ViewModel directly accesses an API, extract it to a Service and route it through a Repository.
  1. 分析功能需求 评估所需功能,确定必要的数据模型、服务和UI状态。 请停止并询问用户: "请提供该功能的具体数据模型、API端点或本地存储需求,并确认是否存在需要专用领域(UseCase)层的复杂业务逻辑。"
  2. 实现数据层:服务 创建无状态服务类来封装外部API或本地存储,该类不得包含业务逻辑或状态。
    dart
    class SharedPreferencesService {
      static const String _kDarkMode = 'darkMode';
    
      Future<void> setDarkMode(bool value) async {
        final prefs = await SharedPreferences.getInstance();
        await prefs.setBool(_kDarkMode, value);
      }
    
      Future<bool> isDarkMode() async {
        final prefs = await SharedPreferences.getInstance();
        return prefs.getBool(_kDarkMode) ?? false;
      }
    }
  3. 实现数据层:Repositories 创建Repository作为唯一可信数据源。Repository调用服务,使用
    Result
    对象处理错误,并暴露领域模型或流。
    dart
    class ThemeRepository {
      ThemeRepository(this._service);
    
      final _darkModeController = StreamController<bool>.broadcast();
      final SharedPreferencesService _service;
    
      Future<Result<bool>> isDarkMode() async {
        try {
          final value = await _service.isDarkMode();
          return Result.ok(value);
        } on Exception catch (e) {
          return Result.error(e);
        }
      }
    
      Future<Result<void>> setDarkMode(bool value) async {
        try {
          await _service.setDarkMode(value);
          _darkModeController.add(value);
          return Result.ok(null);
        } on Exception catch (e) {
          return Result.error(e);
        }
      }
    
      Stream<bool> observeDarkMode() => _darkModeController.stream;
    }
  4. 实现UI层:ViewModels 创建
    ChangeNotifier
    来管理UI状态。使用命令模式处理用户交互和异步Repository调用。
    dart
    class ThemeSwitchViewModel extends ChangeNotifier {
      ThemeSwitchViewModel(this._themeRepository) {
        load = Command0(_load)..execute();
        toggle = Command0(_toggle);
      }
    
      final ThemeRepository _themeRepository;
      bool _isDarkMode = false;
    
      bool get isDarkMode => _isDarkMode;
    
      late final Command0<void> load;
      late final Command0<void> toggle;
    
      Future<Result<void>> _load() async {
        final result = await _themeRepository.isDarkMode();
        if (result is Ok<bool>) {
          _isDarkMode = result.value;
        }
        notifyListeners();
        return result;
      }
    
      Future<Result<void>> _toggle() async {
        _isDarkMode = !_isDarkMode;
        final result = await _themeRepository.setDarkMode(_isDarkMode);
        notifyListeners();
        return result;
      }
    }
  5. 实现UI层:Views 创建
    StatelessWidget
    ,通过
    ListenableBuilder
    监听ViewModel。View不得包含任何业务逻辑。
    dart
    class ThemeSwitch extends StatelessWidget {
      const ThemeSwitch({super.key, required this.viewmodel});
    
      final ThemeSwitchViewModel viewmodel;
    
      
      Widget build(BuildContext context) {
        return Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16.0),
          child: Row(
            children: [
              const Text('Dark Mode'),
              ListenableBuilder(
                listenable: viewmodel,
                builder: (context, _) {
                  return Switch(
                    value: viewmodel.isDarkMode,
                    onChanged: (_) {
                      viewmodel.toggle.execute();
                    },
                  );
                },
              ),
            ],
          ),
        );
      }
    }
  6. 依赖注入 在应用或路由层级通过构造函数注入或类似
    provider
    的依赖注入框架注入依赖。
    dart
    void main() {
      runApp(
        MainApp(
          themeRepository: ThemeRepository(SharedPreferencesService()),
        ),
      );
    }
  7. 验证与修复 对照约束检查生成的实现。确保数据严格向下流动(Repository -> ViewModel -> View),事件严格向上流动(View -> ViewModel -> Repository)。如果View包含数据修改逻辑,将其提取到ViewModel。如果ViewModel直接访问API,将其提取到Service并通过Repository流转。

Constraints

约束

  • No Logic in Views: Views must only contain layout logic, simple conditional rendering based on ViewModel state, and routing.
  • Unidirectional Data Flow: Data must only flow from the Data Layer to the UI Layer. UI events must trigger ViewModel commands.
  • Single Source of Truth: Repositories are the only classes permitted to mutate application data.
  • Service Isolation: ViewModels must never interact directly with Services. They must communicate exclusively through Repositories (or UseCases).
  • Stateless Services: Service classes must not hold any state. Their sole responsibility is wrapping external APIs or local storage mechanisms.
  • Immutable Models: Domain models passed from Repositories to ViewModels must be immutable.
  • Error Handling: Repositories must catch exceptions from Services and return explicit
    Result
    (Ok/Error) objects to the ViewModels.
  • 视图中无逻辑: View仅能包含布局逻辑、基于ViewModel状态的简单条件渲染和路由逻辑。
  • 单向数据流: 数据仅能从数据层流向UI层,UI事件必须触发ViewModel命令。
  • 唯一可信数据源: 只有Repository类可以修改应用数据。
  • 服务隔离: ViewModel永远不能直接与Service交互,必须仅通过Repository(或UseCases)通信。
  • 无状态服务: 服务类不能持有任何状态,其唯一职责是封装外部API或本地存储机制。
  • 不可变模型: 从Repository传递到ViewModel的领域模型必须是不可变的。
  • 错误处理: Repository必须捕获来自Service的异常,并向ViewModel返回明确的
    Result
    (Ok/Error)对象。