关于 flutter-app-architecture
面向 Flutter 开发者的架构技能,适用于新建或重构应用。内容涵盖分层职责、MVVM、依赖注入和单向数据流。
主要功能
- 01UI、逻辑与数据层指导
- 02MVVM 组件职责说明
- 03基于构造函数的依赖注入
- 04以 Repository 作为数据唯一来源
- 05存储与离线优先指导
适用场景
- 01搭建分层 Flutter 应用
- 02重构 ViewModel、Repository 或 Service
- 03为组件配置依赖
- 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. 工作流:搭建新功能
- 创建 Service — 实现 API 封装,并解析带类型的响应。
- 创建 Repository — 注入 Service,并实现缓存和错误处理逻辑。
- 创建 ViewModel — 注入 Repository,并暴露 UI 状态和命令。
- 创建 View — 绑定到 ViewModel,呈现状态并分发事件。
- 配置 DI — 在依赖注入容器中注册所有组件。
- 验证 — 确认 View 从不直接访问 Service,且数据遵循单向流动。
6. 数据存储
- 使用键值存储(例如
shared_preferences)保存配置和偏好设置。 - 使用 SQL 存储(例如
drift、sqflite)保存复杂的关系型数据。 - 实现乐观更新:在服务器确认之前先更新 UI,以提升感知响应速度。
- 通过在 Repository 中组合本地和远程数据源,支持离线优先。
7. 编码约定
- 尽可能使用
StatelessWidget;避免不必要的StatefulWidget。 - 保持构建方法简单,并专注于渲染。
- 字段和顶层变量优先使用
final。类支持时,优先使用const构造函数。 - 对公共 API 优先使用显式类型(例如,使用
Command0<void>而不是动态签名)。 - 使用描述性常量名称(例如,使用
_todoTableName而不是_kTableTodo)。
参考资料
评论
支持 Markdown