SKILL.md
readonlyread-only
name
flutter-apply-architecture-best-practices
description
使用推薦的分層方法(UI、邏輯、資料)來架構 Flutter 應用程式。適用於建立新專案或重構以提升可擴展性。
架構 Flutter 應用程式
目錄
架構層級
透過將應用程式劃分為不同的層級來強制執行嚴格的關注點分離。切勿將 UI 渲染與業務邏輯或資料擷取混在一起。
UI 層(表現層)
實作 MVVM(Model-View-ViewModel)模式來管理 UI 狀態與邏輯。
- Views: 撰寫可重複使用、精簡的 widget。將 View 中的邏輯限制在 UI 特定的操作(例如動畫、佈局約束、簡單路由)。從 ViewModel 傳入所有必要的資料。
- ViewModels: 管理 UI 狀態並處理使用者互動。繼承
ChangeNotifier(或使用Listenable)來暴露狀態。向 View 暴露不可變的狀態快照。透過建構子將 Repository 注入 ViewModel。
資料層
實作 Repository 模式來隔離資料存取邏輯,並建立單一事實來源。
- Services: 建立無狀態類別來包裝外部 API(HTTP 客戶端、本地資料庫、平台插件)。回傳原始 API 模型或
Result包裝器。 - Repositories: 使用一個或多個 Services。將原始 API 模型轉換為乾淨的領域模型。處理快取、離線同步和重試邏輯。向 ViewModel 暴露領域模型。
邏輯層(領域層 - 可選)
- Use Cases: 僅在應用程式包含複雜的業務邏輯,導致 ViewModel 過於龐大,或邏輯需要在多個 ViewModel 之間重複使用時,才實作此層級。將此邏輯提取到專門的 Use Case(互動器)類別中,這些類別位於 ViewModel 和 Repository 之間。
專案結構
使用混合方法組織程式碼庫:按功能分組 UI 元件,按類型分組資料/領域元件。
lib/
├── data/
│ ├── models/ # API 模型
│ ├── repositories/ # Repository 實作
│ └── services/ # API 客戶端、本地儲存包裝器
├── domain/
│ ├── models/ # 乾淨的領域模型
│ └── use_cases/ # 可選的業務邏輯類別
└── ui/
├── core/ # 共用 widget、主題、排版
└── features/
└── [feature_name]/
├── view_models/
└── views/
工作流程:實作新功能
在應用程式中新增功能時,請遵循此順序工作流程。複製核取清單以追蹤進度。
任務進度
- [ ] 步驟 1:定義領域模型。 使用
freezed或built_value為該功能建立不可變的資料類別。 - [ ] 步驟 2:實作 Services。 建立或更新 Service 類別以處理外部 API 通訊。
- [ ] 步驟 3:實作 Repositories。 建立 Repository 以使用 Services 並回傳領域模型。
- [ ] 步驟 4:應用條件邏輯(領域層)。
- 如果該功能需要複雜的資料轉換或跨 Repository 邏輯: 建立 Use Case 類別。
- 如果該功能是簡單的 CRUD 操作: 跳至步驟 5。
- [ ] 步驟 5:實作 ViewModel。 建立繼承
ChangeNotifier的 ViewModel。注入所需的 Repositories/Use Cases。暴露不可變的狀態和命令方法。 - [ ] 步驟 6:實作 View。 建立 UI widget。使用
ListenableBuilder或AnimatedBuilder來監聽 ViewModel 的變更。 - [ ] 步驟 7:注入依賴。 在依賴注入容器(例如
provider或get_it)中註冊新的 Service、Repository 和 ViewModel。 - [ ] 步驟 8:執行驗證器。 對 ViewModel 和 Repository 執行單元測試。
- 反饋迴圈: 執行測試 -> 檢視失敗 -> 修正邏輯 -> 重新執行直到通過。
範例
資料層:Service 和 Repository
// 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 層:ViewModel 和 View
// 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;
@override
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('找不到使用者'));
}
return Column(
children: [
Text(user.name),
ElevatedButton(
onPressed: () => viewModel.loadProfile(user.id),
child: const Text('重新整理'),
),
],
);
},
);
}
}






