flutter-architecture
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFlutter 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 for dependency injection and for reactive UI updates.
providerListenableBuilder使用MVVM模式、单向数据流以及UI、领域、数据层之间严格的关注点分离,实现可扩展、可维护的Flutter应用架构。假设标准Flutter环境使用进行依赖注入,使用实现响应式UI更新。
providerListenableBuilderDecision Logic
决策逻辑
Before implementing a feature, evaluate the architectural requirements using the following logic:
- Data Source:
- If interacting with an external API -> Create a Remote Service.
- If interacting with local storage (SQL/Key-Value) -> Create a Local Service.
- 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.
在实现功能前,使用以下逻辑评估架构要求:
- 数据源:
- 如果需要与外部API交互 -> 创建远程服务。
- 如果需要与本地存储(SQL/键值对)交互 -> 创建本地服务。
- 业务逻辑复杂度:
- 如果功能需要合并多个Repository的数据,或包含高度复杂的可复用逻辑 -> 实现领域层(UseCases)。
- 如果功能是标准CRUD或简单数据展示 -> 跳过领域层,ViewModel直接与Repository通信。
Instructions
使用说明
-
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."
-
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; } } -
Implement the Data Layer: Repositories Create a repository to act as the single source of truth. The repository consumes the service, handles errors usingobjects, and exposes domain models or streams.
Resultdartclass 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; } -
Implement the UI Layer: ViewModels Create ato manage UI state. Use the Command pattern to handle user interactions and asynchronous repository calls.
ChangeNotifierdartclass 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; } } -
Implement the UI Layer: Views Create athat observes the ViewModel using
StatelessWidget. The View must contain zero business logic.ListenableBuilderdartclass 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(); }, ); }, ), ], ), ); } } -
Wire Dependencies Inject the dependencies at the application or route level using constructor injection or a dependency injection framework like.
providerdartvoid main() { runApp( MainApp( themeRepository: ThemeRepository(SharedPreferencesService()), ), ); } -
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.
-
分析功能需求 评估所需功能,确定必要的数据模型、服务和UI状态。 请停止并询问用户: "请提供该功能的具体数据模型、API端点或本地存储需求,并确认是否存在需要专用领域(UseCase)层的复杂业务逻辑。"
-
实现数据层:服务 创建无状态服务类来封装外部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; } } -
实现数据层:Repositories 创建Repository作为唯一可信数据源。Repository调用服务,使用对象处理错误,并暴露领域模型或流。
Resultdartclass 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; } -
实现UI层:ViewModels 创建来管理UI状态。使用命令模式处理用户交互和异步Repository调用。
ChangeNotifierdartclass 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; } } -
实现UI层:Views 创建,通过
StatelessWidget监听ViewModel。View不得包含任何业务逻辑。ListenableBuilderdartclass 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(); }, ); }, ), ], ), ); } } -
依赖注入 在应用或路由层级通过构造函数注入或类似的依赖注入框架注入依赖。
providerdartvoid main() { runApp( MainApp( themeRepository: ThemeRepository(SharedPreferencesService()), ), ); } -
验证与修复 对照约束检查生成的实现。确保数据严格向下流动(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 (Ok/Error) objects to the ViewModels.
Result
- 视图中无逻辑: View仅能包含布局逻辑、基于ViewModel状态的简单条件渲染和路由逻辑。
- 单向数据流: 数据仅能从数据层流向UI层,UI事件必须触发ViewModel命令。
- 唯一可信数据源: 只有Repository类可以修改应用数据。
- 服务隔离: ViewModel永远不能直接与Service交互,必须仅通过Repository(或UseCases)通信。
- 无状态服务: 服务类不能持有任何状态,其唯一职责是封装外部API或本地存储机制。
- 不可变模型: 从Repository传递到ViewModel的领域模型必须是不可变的。
- 错误处理: Repository必须捕获来自Service的异常,并向ViewModel返回明确的(Ok/Error)对象。
Result