SKILL.md
唯讀
名稱
flutter-apply-architecture-best-practices
描述
使用官方推薦的分層架構(介面層、邏輯層、資料層)來設計 Flutter 應用程式架構。適合在規劃新專案結構或進行擴充性重構時使用。
Flutter 應用程式架構設計
目錄
架構分層
透過將應用程式劃分為明確的層級,嚴格執行「關注點分離」(Separation of Concerns)。切勿將 UI 渲染與商業邏輯或資料擷取混在一起。
UI 層(展示層)
採用 MVVM(Model-View-ViewModel)模式管理 UI 狀態與邏輯。
- Views: 撰寫可複用且精簡的 Widget。View 中的邏輯應僅限於 UI 專屬操作(如動畫、版面限制、簡單路由)。所有所需資料皆由 ViewModel 傳入。
- ViewModels: 管理 UI 狀態並處理使用者互動。繼承
ChangeNotifier(或使用Listenable)來對外暴露狀態。提供不可變的狀態快照給 View。透過建構子將 Repository 注入至 ViewModel 中。
資料層
實作 Repository 模式以隔離資料存取邏輯,建立單一事實來源(Single Source of Truth)。
- Services: 建立無狀態(stateless)類別來封裝外部 API(HTTP 用戶端、本機資料庫、平台外掛程式)。回傳原始 API 模型或
Result包裝物件。 - Repositories: 整合一個或多個 Service。將原始 API 模型轉換為乾淨的 Domain Model(領域模型)。負責處理快取、離線同步與重試邏輯,並將 Domain Model 暴露給 ViewModel。
邏輯層(Domain 領域層 - 可選)
- Use Cases: 僅在應用程式包含複雜商業邏輯(避免 ViewModel 過於臃腫),或是邏輯需要在多個 ViewModel 之間複用時才需要實作此層。將這些邏輯抽離為獨立的 Use Case(Interactor)類別,介於 ViewModel 與 Repository 之間。
專案結構
採用混合式結構整理程式碼庫:UI 元件依功能(Feature)分組,Data 與 Domain 元件則依類型分組。
lib/
├── data/
│ ├── models/ # API 模型
│ ├── repositories/ # Repository 實作
│ └── services/ # API 用戶端、本機儲存封裝
├── domain/
│ ├── models/ # 乾淨的領域模型 (Domain models)
│ └── use_cases/ # 可選的商業邏輯類別
└── ui/
├── core/ # 共用 Widget、主題、字型排版
└── features/
└── [feature_name]/
├── view_models/
└── views/
工作流程:開發新功能
在應用程式中新增功能時,請遵循以下步驟流程。可複製此核取清單以追蹤開發進度。
任務進度
- [ ] 步驟 1:定義 Domain Model。 使用
freezed或built_value為該功能建立不可變(immutable)的資料類別。 - [ ] 步驟 2:實作 Service。 建立或更新 Service 類別以處理外部 API 通訊。
- [ ] 步驟 3:實作 Repository。 建立 Repository 來呼叫 Service 並回傳 Domain Model。
- [ ] 步驟 4:套用條件邏輯(Domain 層)。
- 若該功能需要複雜的資料轉換或跨 Repository 邏輯: 建立 Use Case 類別。
- 若該功能僅為簡單的 CRUD 操作: 略過此步驟,直接進入步驟 5。
- [ ] 步驟 5:實作 ViewModel。 建立繼承自
ChangeNotifier的 ViewModel,注入所需的 Repository/Use Case,並暴露不可變狀態與操作方法。 - [ ] 步驟 6:實作 View。 建立 UI Widget,使用
ListenableBuilder或AnimatedBuilder監聽 ViewModel 的變更。 - [ ] 步驟 7:注入依賴(Dependency Injection)。 在依賴注入容器(如
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(單一事實來源,回傳 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); // 轉換為 Domain Model
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('User not found'));
}
return Column(
children: [
Text(user.name),
ElevatedButton(
onPressed: () => viewModel.loadProfile(user.id),
child: const Text('Refresh'),
),
],
);
},
);
}
}






