Flutter项目架构设计:从业务分层到代码复用

Flutter项目架构设计:从业务分层到代码复用

Flutter 项目做到一定规模后,代码组织方式会直接影响开发效率和后期维护成本。之前接手过一个项目,页面组件直接调接口,状态和 UI 全部耦合在一起,加一个字段要改七八个文件。

整理一下 Flutter 项目的分层架构设计思路,以及怎么在实际项目中落地。

常见架构模式

Flutter 生态里常见的架构模式有几种:

MVC:最原始的分层

Model-View-Controller,UI 层负责展示,Controller 负责处理输入,Model 负责数据。

Flutter 的 StatefulWidget 其实天然就是 MVC:Widget 是 View,State 是 Controller,数据可以放在 State 里也可以单独抽成 Model。但实际项目里 MVC 容易变成 Mass View Controller,所有逻辑都堆在 State 里。

MVP:View 和 Presenter 分离

View 负责 UI 展示,Presenter 负责业务逻辑,Model 只负责数据结构。View 和 Presenter 通过接口通信。

Flutter 实现 MVP:

// View 接口
abstract class LoginView {
  void showLoading();
  void hideLoading();
  void showError(String message);
  void navigateToHome();
}

// Presenter
class LoginPresenter {
  final LoginView view;
  final AuthService authService;

  LoginPresenter(this.view, this.authService);

  Future<void> login(String phone, String code) async {
    view.showLoading();
    try {
      await authService.login(phone, code);
      view.hideLoading();
      view.navigateToHome();
    } catch (e) {
      view.hideLoading();
      view.showError(e.toString());
    }
  }
}

// Widget 实现 View
class LoginPage extends StatefulWidget {
  
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> implements LoginView {
  late LoginPresenter _presenter;
  bool _isLoading = false;

  
  void initState() {
    super.initState();
    _presenter = LoginPresenter(this, AuthService());
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: _isLoading
          ? Center(child: CircularProgressIndicator())
          : ElevatedButton(
              onPressed: () => _presenter.login('123', '456'),
              child: Text('登录'),
            ),
    );
  }

  
  void showLoading() => setState(() => _isLoading = true);
  
  void hideLoading() => setState(() => _isLoading = false);
  
  void showError(String message) => ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
  
  void navigateToHome() => Navigator.of(context).pushReplacementNamed('/home');
}

MVP 的问题是 View 和 Presenter 的接口需要维护,当页面复杂时接口数量会爆炸。

Clean Architecture:洋葱圈架构

Clean Architecture 是 Robert C. Martin 提出的架构模式,核心思想是分层依赖原则:外层依赖内层,内层不知道外层的存在。

Flutter 版本的 Clean Architecture 分层:

lib/
├── core/                    # 核心层:工具、常量、错误定义
│   ├── constants/
│   ├── errors/
│   └── utils/
├── features/               # 功能模块(按业务功能划分)
│   └── auth/
│       ├── data/           # 数据层
│       │   ├── datasources/
│       │   ├── models/
│       │   └── repositories/
│       ├── domain/         # 领域层
│       │   ├── entities/
│       │   ├── repositories/
│       │   └── usecases/
│       └── presentation/   # 展示层
│           ├── bloc/
│           ├── pages/
│           └── widgets/
└── injection_container.dart # 依赖注入

这种分层的优势是业务逻辑和框架解耦,换 UI 框架(比如从 Flutter 换成 RN)时 domain 层不需要改。

劣势是层数多,代码量大,中小项目可能过于重量。

实际项目的分层方案

结合实践经验,我目前倾向的方案是三层 + 领域驱动

lib/
├── core/                   # 核心工具和配置
│   ├── api/                # API 客户端封装
│   ├── errors/             # 统一错误处理
│   ├── storage/            # 本地存储
│   └── utils/              # 工具函数
├── shared/                 # 跨功能共享
│   ├── widgets/            # 通用组件
│   ├── models/             # 通用数据模型
│   └── providers/          # 全局状态管理
├── features/              # 按业务功能模块
│   ├── auth/
│   │   ├── models/         # 数据模型
│   │   ├── services/       # 业务服务
│   │   ├── providers/     # 状态管理
│   │   └── pages/
│   ├── product/
│   │   ├── models/
│   │   ├── services/
│   │   ├── providers/
│   │   └── pages/
└── main.dart

这个方案比 Clean Architecture 轻,比 MVC 清晰。

数据流设计

分层之后,关键是怎么设计数据流。我现在的做法是:单向数据流 + 依赖注入

单向数据流

User Action → Provider/Bloc → Service → Repository → DataSource → API/DB
     ↑                                                              ↓
     └────────────────── UI Rebuild ←──────────────────────────────┘

用户操作触发 Provider/Bloc 的方法,Provider/Bloc 调用 Service 执行业务逻辑,Service 调用 Repository 获取数据,数据经过 DataSource 处理后从 API 或本地存储返回,UI 响应状态变化重建。

以商品列表为例:

// 1. Entity:领域实体
class Product {
  final String id;
  final String name;
  final double price;
  final String imageUrl;

  const Product({
    required this.id,
    required this.name,
    required this.price,
    required this.imageUrl,
  });
}
// 2. Repository 接口
abstract class ProductRepository {
  Future<List<Product>> getProducts({int page, int pageSize});
  Future<Product> getProductById(String id);
}
// 3. Repository 实现
class ProductRepositoryImpl implements ProductRepository {
  final ApiClient _apiClient;

  ProductRepositoryImpl(this._apiClient);

  
  Future<List<Product>> getProducts({int page = 1, int pageSize = 20}) async {
    final response = await _apiClient.get(
      '/products',
      queryParameters: {'page': page, 'pageSize': pageSize},
    );
    return (response['data'] as List)
        .map((json) => ProductModel.fromJson(json).toEntity())
        .toList();
  }

  
  Future<Product> getProductById(String id) async {
    final response = await _apiClient.get('/products/$id');
    return ProductModel.fromJson(response['data']).toEntity();
  }
}
// 4. Service:业务逻辑层
class ProductService {
  final ProductRepository _repository;

  ProductService(this._repository);

  Future<List<Product>> getProductList({
    required int page,
    int pageSize = 20,
    String? categoryId,
  }) async {
    // 可以在这里加缓存逻辑
    final products = await _repository.getProducts(
      page: page,
      pageSize: pageSize,
    );

    // 业务筛选
    if (categoryId != null) {
      return products.where((p) => p.categoryId == categoryId).toList();
    }
    return products;
  }

  // 业务计算
  double calculateDiscount(Product product, UserLevel level) {
    switch (level) {
      case UserLevel.vip:
        return product.price * 0.8;
      case UserLevel.svip:
        return product.price * 0.7;
      case UserLevel.normal:
        return product.price;
    }
  }
}
// 5. Provider:状态管理层
class ProductListProvider extends StateNotifier<ProductListState> {
  final ProductService _service;

  ProductListProvider(this._service) : super(ProductListState.initial());

  Future<void> loadProducts({bool refresh = false}) async {
    if (state.isLoading) return;

    state = state.copyWith(isLoading: true, isRefreshing: refresh);

    try {
      final page = refresh ? 1 : state.currentPage + 1;
      final products = await _service.getProductList(
        page: page,
        pageSize: 20,
      );

      state = state.copyWith(
        isLoading: false,
        isRefreshing: false,
        products: refresh ? products : [...state.products, ...products],
        currentPage: page,
        hasMore: products.length == 20,
      );
    } catch (e) {
      state = state.copyWith(
        isLoading: false,
        isRefreshing: false,
        error: e.toString(),
      );
    }
  }
}
// 6. UI 层
class ProductListPage extends ConsumerWidget {
  const ProductListPage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final state = ref.watch(productListProvider);

    if (state.isLoading && state.products.isEmpty) {
      return Center(child: CircularProgressIndicator());
    }

    return NotificationListener<ScrollNotification>(
      onNotification: (notification) {
        if (notification is ScrollEndNotification &&
            notification.metrics.extentAfter < 200 &&
            state.hasMore) {
          ref.read(productListProvider.notifier).loadProducts();
        }
        return false;
      },
      child: ListView.builder(
        itemCount: state.products.length,
        itemBuilder: (context, index) {
          return ProductCard(product: state.products[index]);
        },
      ),
    );
  }
}

依赖注入

分层后各层之间有依赖关系,需要统一管理。我倾向用 get_it 做 Service Locator:

import 'package:get_it/get_it.dart';

final getIt = GetIt.instance;

void setupDependencies() {
  // API Client
  getIt.registerLazySingleton<ApiClient>(
    () => ApiClient(baseUrl: 'https://api.example.com'),
  );

  // Repositories
  getIt.registerLazySingleton<ProductRepository>(
    () => ProductRepositoryImpl(getIt<ApiClient>()),
  );

  // Services
  getIt.registerLazySingleton<ProductService>(
    () => ProductService(getIt<ProductRepository>()),
  );

  // Providers
  getIt.registerFactory<ProductListProvider>(
    () => ProductListProvider(getIt<ProductService>()),
  );
}

页面里通过 Provider 获取:

class ProductListPage extends ConsumerWidget {
  const ProductListPage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    return ChangeNotifierProvider(
      create: (_) => getIt<ProductListProvider>()..loadProducts(),
      child: Scaffold(...),
    );
  }
}

代码复用策略

分层之后,代码复用变得更容易:

共享 Widget

// shared/widgets/product_card.dart
class ProductCard extends StatelessWidget {
  final Product product;
  final VoidCallback? onTap;

  const ProductCard({
    super.key,
    required this.product,
    this.onTap,
  });

  
  Widget build(BuildContext context) {
    return InkWell(
      onTap: onTap,
      child: Column(
        children: [
          CachedNetworkImage(imageUrl: product.imageUrl),
          Text(product.name),
          Text(${product.price}'),
        ],
      ),
    );
  }
}

共享 Model 转换

每个 API 响应都要做 JSON 到 Model 的转换,封装一个 base 类减少重复:

// base model
class BaseModel {
  final String id;
  final DateTime createdAt;
  final DateTime updatedAt;

  BaseModel.fromJson(Map<String, dynamic> json)
      : id = json['id'],
        createdAt = DateTime.parse(json['created_at']),
        updatedAt = DateTime.parse(json['updated_at']);
}

// 子类 model
class ProductModel extends BaseModel {
  final String name;
  final double price;

  ProductModel.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        price = (json['price'] as num).toDouble(),
        super.fromJson(json);

  // 转换为 Entity
  Product toEntity() => Product(
    id: id,
    name: name,
    price: price,
  );
}

总结

Flutter 项目架构设计的核心原则:

  1. 分层清晰,依赖单向:上层依赖下层,下层不感知上层
  2. 数据模型和 UI 模型分离:API 返回的模型(Model)和 UI 使用的实体(Entity)分开
  3. 业务逻辑统一封装:跨页面复用的业务逻辑放在 Service 层,不要散在 Provider/Bloc 里
  4. 状态管理统一:选一种状态管理方案用到底,不要混用 Provider、Riverpod、BLoC
  5. 依赖注入统一管理:用 Service Locator 或 DI 框架,所有依赖在一个地方配置

架构没有银弹,关键是团队一致性和项目匹配度。小团队小项目可以简化分层,专注业务快速落地;大团队大项目需要更严格的分层来保证代码质量和可维护性。

最后更新 4/20/2026, 4:48:48 AM