
一個架構良好的 Flutter 應用程式,決定了代碼庫是能夠從容擴展,還是淪為維護噩夢。良好的架構令你的應用程式在需求演變時更易於測試、除錯及擴展。
本指南涵蓋經過驗證的架構模式、依賴注入策略及代碼組織技巧,助你構建可維護、可測試、足以應付企業級開發的 Flutter 應用程式。
CLEAN ARCHITECTURE:黃金標準
Clean Architecture 將應用程式劃分為職責分明、依賴清晰的多個分層,業務邏輯獨立於框架、UI 及外部數據來源。
// Domain Layer - Business Logic
abstract class UserRepository {
Future<User> getUser(String id);
Future<void> updateUser(User user);
}
class User {
final String id;
final String name;
final String email;
const User({
required this.id,
required this.name,
required this.email,
});
}
class GetUserUseCase {
final UserRepository repository;
GetUserUseCase(this.repository);
Future<User> call(String userId) {
return repository.getUser(userId);
}
}
// Data Layer - External Data Sources
class ApiUserRepository implements UserRepository {
final ApiClient apiClient;
ApiUserRepository(this.apiClient);
@override
Future<User> getUser(String id) async {
final response = await apiClient.get('/users/$id');
return User.fromJson(response.data);
}
@override
Future<void> updateUser(User user) async {
await apiClient.put('/users/${user.id}', user.toJson());
}
}
// Presentation Layer - UI Logic
class UserBloc extends Bloc<UserEvent, UserState> {
final GetUserUseCase getUserUseCase;
UserBloc(this.getUserUseCase) : super(UserInitial()) {
on<LoadUser>(_onLoadUser);
}
Future<void> _onLoadUser(LoadUser event, Emitter<UserState> emit) async {
emit(UserLoading());
try {
final user = await getUserUseCase(event.userId);
emit(UserLoaded(user));
} catch (e) {
emit(UserError(e.toString()));
}
}
}領域層
- 業務實體
- 用例
- Repository 介面
- 業務規則
數據層
- Repository 實現
- 數據來源(API、數據庫)
- 數據模型
- 外部服務
展示層
- UI Widget
- 狀態管理
- ViewModel / BLoC
- 導航
MVVM:MODEL-VIEW-VIEWMODEL
MVVM 透過 ViewModel 將 UI 邏輯與業務邏輯分離,特別適合 UI 互動複雜的應用程式,與 Provider 或 Riverpod 搭配使用效果極佳。
// Model
class Product {
final String id;
final String name;
final double price;
final String imageUrl;
Product({
required this.id,
required this.name,
required this.price,
required this.imageUrl,
});
}
// ViewModel
class ProductListViewModel extends ChangeNotifier {
final ProductRepository _repository;
ProductListViewModel(this._repository);
List<Product> _products = [];
bool _isLoading = false;
String? _error;
List<Product> get products => _products;
bool get isLoading => _isLoading;
String? get error => _error;
Future<void> loadProducts() async {
_setLoading(true);
_setError(null);
try {
_products = await _repository.getProducts();
notifyListeners();
} catch (e) {
_setError(e.toString());
} finally {
_setLoading(false);
}
}
Future<void> addToCart(Product product) async {
try {
await _repository.addToCart(product.id);
// Show success message or update UI
} catch (e) {
_setError('Failed to add product to cart');
}
}
void _setLoading(bool loading) {
_isLoading = loading;
notifyListeners();
}
void _setError(String? error) {
_error = error;
notifyListeners();
}
}
// View
class ProductListView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ProductListViewModel(context.read<ProductRepository>())
..loadProducts(),
child: Scaffold(
appBar: AppBar(title: Text('Products')),
body: Consumer<ProductListViewModel>(
builder: (context, viewModel, child) {
if (viewModel.isLoading) {
return Center(child: CircularProgressIndicator());
}
if (viewModel.error != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Error: ${viewModel.error}'),
ElevatedButton(
onPressed: viewModel.loadProducts,
child: Text('Retry'),
),
],
),
);
}
return ListView.builder(
itemCount: viewModel.products.length,
itemBuilder: (context, index) {
final product = viewModel.products[index];
return ProductTile(
product: product,
onAddToCart: () => viewModel.addToCart(product),
);
},
);
},
),
),
);
}
}MVVM 的優勢
- UI 與業務邏輯清晰分離
- ViewModel 易於進行單元測試
- ViewModel 可在不同畫面重用
- 與反應式編程配合良好
依賴注入模式
依賴注入令代碼更易測試、更具彈性,並符合依賴反轉原則。以下是 Flutter 應用程式中行之有效的模式。
// Service Locator Pattern with GetIt
final GetIt sl = GetIt.instance;
void setupServiceLocator() {
// External services
sl.registerLazySingleton<ApiClient>(() => ApiClient());
sl.registerLazySingleton<DatabaseHelper>(() => DatabaseHelper());
// Repositories
sl.registerLazySingleton<UserRepository>(
() => ApiUserRepository(sl<ApiClient>()),
);
sl.registerLazySingleton<ProductRepository>(
() => ApiProductRepository(sl<ApiClient>()),
);
// Use cases
sl.registerLazySingleton<GetUserUseCase>(
() => GetUserUseCase(sl<UserRepository>()),
);
sl.registerLazySingleton<GetProductsUseCase>(
() => GetProductsUseCase(sl<ProductRepository>()),
);
// ViewModels
sl.registerFactory<UserViewModel>(
() => UserViewModel(sl<GetUserUseCase>()),
);
sl.registerFactory<ProductListViewModel>(
() => ProductListViewModel(sl<GetProductsUseCase>()),
);
}
// Provider with dependency injection
class UserPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<UserViewModel>(
create: (_) => sl<UserViewModel>(),
child: UserView(),
);
}
}
// Factory pattern for complex objects
abstract class RepositoryFactory {
UserRepository createUserRepository();
ProductRepository createProductRepository();
}
class ApiRepositoryFactory implements RepositoryFactory {
final ApiClient apiClient;
ApiRepositoryFactory(this.apiClient);
@override
UserRepository createUserRepository() {
return ApiUserRepository(apiClient);
}
@override
ProductRepository createProductRepository() {
return ApiProductRepository(apiClient);
}
}
// Environment-based configuration
class AppConfig {
static const String _baseUrl = String.fromEnvironment(
'BASE_URL',
defaultValue: 'https://api.example.com',
);
static const bool _isProduction = bool.fromEnvironment(
'PRODUCTION',
defaultValue: false,
);
static String get baseUrl => _baseUrl;
static bool get isProduction => _isProduction;
static bool get isDevelopment => !_isProduction;
}
void setupEnvironmentDependencies() {
if (AppConfig.isDevelopment) {
sl.registerLazySingleton<ApiClient>(
() => MockApiClient(), // Use mock in development
);
} else {
sl.registerLazySingleton<ApiClient>(
() => ApiClient(baseUrl: AppConfig.baseUrl),
);
}
}DI 最佳實踐
- 註冊介面而非具體實現
- 有狀態的物件使用 factory 註冊
- 單例服務盡量保持無狀態
- 在應用程式啟動時完成依賴設定
理想的資料夾結構
條理分明的資料夾結構令代碼導航更直觀,亦有助維持架構邊界。以下是適用於 Flutter 應用程式的可擴展結構。
lib/ ├── core/ # Shared app foundation │ ├── constants/ # App-wide constants │ │ ├── api_constants.dart │ │ ├── app_strings.dart │ │ └── app_colors.dart │ ├── error/ # Error handling │ │ ├── failures.dart │ │ └── exceptions.dart │ ├── network/ # Network layer │ │ ├── api_client.dart │ │ └── network_info.dart │ ├── utils/ # Utility functions │ │ ├── validators.dart │ │ └── formatters.dart │ └── injection_container.dart # Dependency injection setup ├── features/ # Feature-based organization │ ├── authentication/ │ │ ├── data/ │ │ │ ├── datasources/ │ │ │ │ ├── auth_local_datasource.dart │ │ │ │ └── auth_remote_datasource.dart │ │ │ ├── models/ │ │ │ │ └── user_model.dart │ │ │ └── repositories/ │ │ │ └── auth_repository_impl.dart │ │ ├── domain/ │ │ │ ├── entities/ │ │ │ │ └── user.dart │ │ │ ├── repositories/ │ │ │ │ └── auth_repository.dart │ │ │ └── usecases/ │ │ │ ├── login_usecase.dart │ │ │ └── logout_usecase.dart │ │ └── presentation/ │ │ ├── bloc/ │ │ │ ├── auth_bloc.dart │ │ │ ├── auth_event.dart │ │ │ └── auth_state.dart │ │ ├── pages/ │ │ │ ├── login_page.dart │ │ │ └── signup_page.dart │ │ └── widgets/ │ │ ├── login_form.dart │ │ └── password_field.dart │ ├── products/ │ │ ├── data/ │ │ ├── domain/ │ │ └── presentation/ │ └── cart/ │ ├── data/ │ ├── domain/ │ └── presentation/ ├── shared/ # Shared across features │ ├── widgets/ # Reusable UI components │ │ ├── buttons/ │ │ ├── forms/ │ │ └── loading/ │ ├── extensions/ # Dart extensions │ │ ├── string_extensions.dart │ │ └── date_extensions.dart │ └── mixins/ # Reusable behavior │ ├── validation_mixin.dart │ └── loading_mixin.dart ├── config/ # App configuration │ ├── app_config.dart │ ├── routes.dart │ └── themes.dart └── main.dart # App entry point
結構優勢
- 關注點清晰分離
- 功能易於定位和修改
- 強化架構邊界
- 隨團隊規模與複雜度擴展
Flutter 架構原則
關注點分離:每一分層只承擔單一職責——UI 負責呈現,領域層承載業務邏輯,數據層管理外部來源。
依賴反轉:高層模組不應依賴低層模組,兩者都應依賴抽象(介面)。
可測試性優先:設計架構時以方便單元測試為前提。如果代碼難以測試,多半是設計欠佳。
按功能組織:以功能而非技術分層來組織代碼,令開發特定功能時更加得心應手。
漸進式增強:從簡單起步,按需要逐步增加複雜度,不要為尚未出現的需求過度設計。


