返回網誌
架構2025年9月29日安安科技團隊閱讀時間 12 分鐘

Flutter 應用程式架構
最佳實踐

掌握 Flutter 應用程式架構:以整潔的模式、關注點分離及可擴展的代碼結構,令代碼庫隨團隊與需求一同成長。

Flutter 應用程式架構最佳實踐
// 簡報

一個架構良好的 Flutter 應用程式,決定了代碼庫是能夠從容擴展,還是淪為維護噩夢。良好的架構令你的應用程式在需求演變時更易於測試、除錯及擴展。

本指南涵蓋經過驗證的架構模式、依賴注入策略及代碼組織技巧,助你構建可維護、可測試、足以應付企業級開發的 Flutter 應用程式。

CLEAN ARCHITECTURE:黃金標準

Clean Architecture 將應用程式劃分為職責分明、依賴清晰的多個分層,業務邏輯獨立於框架、UI 及外部數據來源。

clean_architecture.dart
// 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 搭配使用效果極佳。

mvvm_pattern.dart
// 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.dart
// 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/
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 架構原則

1

關注點分離:每一分層只承擔單一職責——UI 負責呈現,領域層承載業務邏輯,數據層管理外部來源。

2

依賴反轉:高層模組不應依賴低層模組,兩者都應依賴抽象(介面)。

3

可測試性優先:設計架構時以方便單元測試為前提。如果代碼難以測試,多半是設計欠佳。

4

按功能組織:以功能而非技術分層來組織代碼,令開發特定功能時更加得心應手。

5

漸進式增強:從簡單起步,按需要逐步增加複雜度,不要為尚未出現的需求過度設計。

// 藍圖

準備好規劃你的 Flutter 應用程式架構?

我們的團隊專精於構建可擴展、易維護的 Flutter 架構。讓我們助你設計一個隨業務一同成長的應用程式結構!

聯絡我們
// 訊號增幅

分享這份指南