mcpField
F
flutter-app-architecture

flutter-app-architecture

github.com/evanca开发663231 次安装3 周前更新
代码仓库

关于 flutter-app-architecture

面向 Flutter 开发者的架构技能,适用于新建或重构应用。内容涵盖分层职责、MVVM、依赖注入和单向数据流。

主要功能

  1. 01UI、逻辑与数据层指导
  2. 02MVVM 组件职责说明
  3. 03基于构造函数的依赖注入
  4. 04以 Repository 作为数据唯一来源
  5. 05存储与离线优先指导

适用场景

  1. 01搭建分层 Flutter 应用
  2. 02重构 ViewModel、Repository 或 Service
  3. 03为组件配置依赖
  4. 04为复用场景添加领域逻辑

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/evanca/flutter-ai-rules --skill flutter-app-architecture

安装后在 Claude Code 中输入/flutter-app-architecture确认已加载。

详细介绍

Flutter 应用架构技能

此技能定义如何使用分层架构、恰当的数据流和 MVVM 模式来构建 Flutter 应用,以提高可维护性和可测试性。

适用场景

在以下情况下使用此技能:

  • 使用分层架构搭建新的 Flutter 项目。
  • 创建或重构 ViewModel、Repository 或 Service。
  • 为架构组件配置依赖注入。
  • 在各层之间实现单向数据流。
  • 为复杂业务逻辑或共享用例添加 Domain(逻辑)层。

1. 分层结构

将每个应用划分为 UI 层 和 数据层。仅为复杂应用添加 逻辑(Domain)层。

┌──────────────────────────────────────────────────────────────┐
│   UI Layer    │  Views + ViewModels                           │
├──────────────────────────────────────────────────────────────┤
│  Logic Layer  │  Use Cases / Interactors  (optional)         │
├──────────────────────────────────────────────────────────────┤
│   Data Layer  │  Repositories + Services                     │
└──────────────────────────────────────────────────────────────┘

规则:

  • 只有相邻层之间才能通信。UI 层绝不能直接访问 Service。
  • 数据变更始终在数据层发生(SSOT = Repository)。UI 层和逻辑层不得进行变更。
  • 遵循单向数据流:状态向下流动(数据 → UI),事件向上流动(UI → 数据)。

2. 组件职责

View
  • 描述如何呈现数据;保持逻辑精简且仅与 UI 相关。
  • 响应用户交互时,将事件传递给 ViewModel。
ViewModel
  • 将应用数据转换为 UI 状态,并维护 View 所需的当前状态。
  • 向 View 暴露回调(命令),并从 Repository 获取或转换数据。
class BookingViewModel extends ChangeNotifier {
  final BookingRepository _repo;

  BookingViewModel(this._repo);

  List<Booking> _bookings = [];
  List<Booking> get bookings => List.unmodifiable(_bookings);

  bool _isLoading = false;
  bool get isLoading => _isLoading;

  Future<void> loadBookings() async {
    _isLoading = true;
    notifyListeners();

    _bookings = await _repo.getBookings();
    _isLoading = false;
    notifyListeners();
  }

  Future<void> cancelBooking(String id) async {
    await _repo.cancelBooking(id);
    _bookings = await _repo.getBookings();
    notifyListeners();
  }
}
Repository(单一事实来源)
  • 唯一可以变更其数据的类;所有其他类均从中读取数据。
  • 负责缓存、错误处理和数据刷新逻辑。
  • 将 Service 返回的原始数据转换为领域模型。
class BookingRepository {
  final BookingApiService _apiService;
  final BookingLocalService _localService;

  BookingRepository(this._apiService, this._localService);

  Future<List<Booking>> getBookings() async {
    try {
      final remote = await _apiService.fetchBookings();
      await _localService.cacheBookings(remote);
      return remote;
    } catch (_) {
      return _localService.getCachedBookings();
    }
  }

  Future<void> cancelBooking(String id) async {
    await _apiService.cancelBooking(id);
    await _localService.removeCachedBooking(id);
  }
}
Service
  • 封装 API 端点,并提供异步响应对象。
  • 隔离数据加载,不持有状态。
class BookingApiService {
  final http.Client _client;
  BookingApiService(this._client);

  Future<List<Booking>> fetchBookings() async {
    final response = await _client.get(Uri.parse('/api/bookings'));
    if (response.statusCode != 200) {
      throw HttpException('Failed to load bookings');
    }
    final data = jsonDecode(response.body) as List;
    return data.map((json) => Booking.fromJson(json)).toList();
  }
}

3. 依赖注入

通过构造函数提供依赖。定义抽象接口,以便在测试时替换实现。

// Abstract interface for the repository
abstract class BookingRepository {
  Future<List<Booking>> getBookings();
  Future<void> cancelBooking(String id);
}

// Concrete implementation
class BookingRepositoryImpl implements BookingRepository {
  final BookingApiService _api;
  BookingRepositoryImpl(this._api);

  @override
  Future<List<Booking>> getBookings() => _api.fetchBookings();

  @override
  Future<void> cancelBooking(String id) => _api.cancelBooking(id);
}

4. 用例(Domain 层)

仅在以下情况下引入用例:

  • 逻辑复杂,或无法清晰地放入 UI 层或数据层。
  • 逻辑在多个 ViewModel 之间复用,或需要合并来自多个 Repository 的数据。
class GetUpcomingBookingsUseCase {
  final BookingRepository _bookingRepo;
  final UserRepository _userRepo;

  GetUpcomingBookingsUseCase(this._bookingRepo, this._userRepo);

  Future<List<Booking>> call() async {
    final user = await _userRepo.getCurrentUser();
    final bookings = await _bookingRepo.getBookings();
    return bookings
        .where((b) => b.userId == user.id && b.date.isAfter(DateTime.now()))
        .toList();
  }
}

5. 工作流:搭建新功能

  1. 创建 Service — 实现 API 封装,并解析带类型的响应。
  2. 创建 Repository — 注入 Service,并实现缓存和错误处理逻辑。
  3. 创建 ViewModel — 注入 Repository,并暴露 UI 状态和命令。
  4. 创建 View — 绑定到 ViewModel,呈现状态并分发事件。
  5. 配置 DI — 在依赖注入容器中注册所有组件。
  6. 验证 — 确认 View 从不直接访问 Service,且数据遵循单向流动。

6. 数据存储

  • 使用键值存储(例如 shared_preferences)保存配置和偏好设置。
  • 使用 SQL 存储(例如 drift、sqflite)保存复杂的关系型数据。
  • 实现乐观更新:在服务器确认之前先更新 UI,以提升感知响应速度。
  • 通过在 Repository 中组合本地和远程数据源,支持离线优先。

7. 编码约定

  • 尽可能使用 StatelessWidget;避免不必要的 StatefulWidget。
  • 保持构建方法简单,并专注于渲染。
  • 字段和顶层变量优先使用 final。类支持时,优先使用 const 构造函数。
  • 对公共 API 优先使用显式类型(例如,使用 Command0<void> 而不是动态签名)。
  • 使用描述性常量名称(例如,使用 _todoTableName 而不是 _kTableTodo)。

参考资料

评论

支持 Markdown