ChatGPT Prompts
Flutter ChatGPT Prompts 项目
设置
-
在您的系统中安装 flutter_gen 并运行
flutter_gen -c pubspec.yaml
-
安装 Git Hooks (可选)
./scripts/install-git-hooks.sh
-
运行
make check
当您想确保代码干净且准备好进行代码审查时,此命令非常有用。它执行以下操作:
- 运行 flutter_gen
- 格式化您的代码
- 运行
flutter analyze - 运行单元测试并重新创建所有 golden 测试截图文件
- 运行 dart_code_metrics 检查
架构
该应用程序遵循 Clean Architecture 原则,并分为 4 个模块:

UI
UI 模块包含 Flutter 小部件、动画、路由、页面以及所有负责应用程序外观的代码。此外,重要的是要注意该模块省略了逻辑。
表现层
存放 Presenter 和 PresentationModel 类的位置。
Presenter 决定 **何时** 显示内容。它们会触发 domain 模块的业务逻辑,并用相关数据更新 PresentationModel,然后由 UI 使用。
此外,PresentationModel 存储数据,并通过 ViewModel 接口将其公开给 UI。这确保了 UI 可以以**只读**方式访问数据,并且所有与表示相关的内部内容都是不可见的。
领域层
UseCase、Repository 和 Entity 类。
此模块包含应用程序的业务逻辑。它决定在用户交互发生时 **应该** 执行什么。逻辑被封装在 UseCase 类中,这些类通过存储库与外部世界通信。
存储库接口在 domain 模块内指定,并由 data 模块实现。domain 操作的所有数据都封装在领域实体类中。
数据层
数据模块负责与“外部世界”通信并与第三方库交互。这里我们指定 REST API 交互、数据库访问、SharedPrefs 或外部传感器访问。所有数据都会被翻译成领域实体,这些实体对用于访问它们的库和技术是无关的。
导航
应用程序内的导航使用了 mixin Routes 和 per-page Navigators 的概念。
Navigator
Navigator 是一个包含 0 个或多个 Route mixin 的类,它的唯一工作是打开另一个页面。让我们通过一个例子来看一下。
假设我们有一个针对 LoginPage 的 Navigator,称为 LoginNavigator。
class LoginNavigator with NoRoutes {
AppInitNavigator(this.appNavigator);
@override
final AppNavigator appNavigator;
}
默认情况下,它与 NoRoutes mixin 混合,这是一个占位符,将被其他路由替换。LoginPresenter 使用此 Navigator 打开其他页面。我们可以通过以下方式实现:
class LoginNavigator with DashboardRoute {
// ...
}
正如您所见,我们混合了 DashboardRoute。
Route
下面您可以看到一个路由的实现。
mixin DashboardRoute {
Future<void> openDashboard(DashboardInitialParams initialParams) async {
return appNavigator.push(
OnboardingPageTransition(
getIt<DashboardPage>(param1: initialParams),
),
);
}
AppNavigator get appNavigator;
}
它指定了 openDashboard 方法,现在可以用于 LoginNavigator 以打开 DashboardPage。它在底层使用 appNavigator,这是一个 Flutter 导航的包装器,可以简化导航,更多关于 AppNavigator 的信息请看下面。
这里的要点是,Routes 指定了给定页面的打开方式,而 Navigators 将这些路由聚集在一起,并通过混合它们来暴露给调用者。
AppNavigator 类
AppNavigator 是 Flutter 导航的包装器,允许我们在默认情况下无需提供 BuildContext 即可执行导航。它将使用 MaterialApp 的根导航器,除非您显式提供 BuildContext 以通过 Flutter SDK 的嵌入式 Navigator 小部件实现嵌套导航。
示例
下面您可以找到一个展示登录页面基本场景的流程图。
工具
这是项目中使用的不同工具列表,它们可以增强和帮助我们日常工作。
FVM
为了保持团队所有开发人员一致的 Flutter 和 Dart 版本,我们使用 fvm,它是 Flutter 版本管理器。我们在此仓库下提交了一个配置文件 .fvm/fvm_config.json,其中指定了要使用的确切 Flutter 版本。在您的机器上安装 fvm 后,您可以在项目根目录下发出此命令以安装正确的 Flutter 版本:
fvm install
代码模板
此仓库托管了一些使用 Mason 创建的有用文件模板。您可以在 templates 子目录中找到它们。要使用这些,请安装 Mason(说明在此处)。
Mason 命令
| ?注意? |
|---|
您必须从 templates 子目录运行以下命令! |
首先,运行 mason get 以在本地安装所有模板!
| 命令 | 描述 |
|---|---|
mason get |
将所有模板安装到您的本地计算机。 |
mason list |
显示可用模板列表。 |
mason make page |
执行 page 模板。 |
自定义 lint
我们在 tools/custom_lints/ 文件夹中托管了一套我们自己的自定义 lint。这些是使用 custom_lint 包编写的。要运行 lint,您需要运行以下命令:
fvm flutter pub run custom_lint
您可以在 custom_lint.dart 中找到所有 lint 的列表。
Fluttergen
我们使用 Fluttergen 为我们的资源生成适当的索引文件,这样所有路径都保存在常量中,您就不必记住在需要使用资源时更新它们或手动输入它们。
在您的系统上安装后,您可以运行以下命令:
fluttergen -c pubspec.yaml
在项目根目录下生成 Assets.gen.dart 文件等。
Dart Code Metrics
Dart_code_metrics 是一个托管大量附加自定义 lint 的库。
我们使用以下命令来运行它(但请参考 Makefile 以获取最新的命令列表)。
fvm flutter pub run dart_code_metrics:metrics analyze lib --set-exit-on-violation-level=warning --fatal-style --fatal-performance --fatal-warnings
fvm flutter pub run dart_code_metrics:metrics check-unused-code . --fatal-unused
fvm flutter pub run dart_code_metrics:metrics check-unused-files . --fatal-unused --exclude="{templates/**,.dart_tool/**,lib/generated/**}"
Fastlane
我们在 ios 和 android 子文件夹中使用 fastlane 完成各种任务。两个平台通过引用 tools/fastlane 文件夹中的脚本来重用大量代码。要更深入地了解 Fastlane 的功能,请查看它们各自的 README。
代码最佳实践和指南
Pull Request 清单
| 规则 | 说明 |
|---|---|
| 先审查您自己的 PR | 通读您自己的 PR 有助于发现明显的错误,并为审阅者节省时间。 |
| 确保所有 CI 检查都通过 | CI 旨在捕获格式和 lint 错误,请加以利用,不要强迫他人做机器的工作? |
| 运行应用程序并自行测试 | 在审查 PR 或提交您自己的代码时,请确保运行应用程序并进行测试,以确保代码不会破坏任何内容且运行正常。 |
| 不要害怕提问 | 代码审查不仅是为了发现他人代码中的错误,也是为了确保您理解代码并了解正在发生的事情。如果您有任何不清楚的地方,不要害怕提问,不要认为这是您的错,请提问!? |
通用
| 规则 | 说明 |
|---|---|
| 为方法/构造函数参数和定义使用尾部逗号。 | 这样,每个参数都在单独的一行,在 PR 中添加新参数也更易于阅读。 |
| 优先使用命名参数 | 当使用多个参数时,请考虑使用命名参数,例如:错误:getBalances(true,"1283184"),错误:getBalances(id: "1283184", refresh: true) |
UseCase
- 执行业务逻辑,通过存储库与外部世界通信。
- 应包含单个公共
execute()方法,可选地带有执行业务逻辑的运行时参数。 - 所有编译时依赖项都应通过构造函数注入。
- 切勿在 usecases 中使用 json 类,始终通过存储库进行 API 通信。
Repository
- 应在
domain/repositories包中指定为接口,例如:UserRepository。 - 实现应放在
data/{tech}包中,例如:RestApiUserRepository在data/rest/包中。 - 其工作是抽象与外部世界的通信(API、区块链、数据库、共享首选项)。
- 将域模型转换为数据模型,反之亦然。以便 use cases 仅依赖于域实体。
- 存储库方法应始终以
Future<Either<Failure,Result>>类型的响应形式返回。 Result部分需要是域实体或原始类型,切勿是 json 或特定于库的类!- 正确:
String、User、int - 错误:
UserJson、FirebaseUser、FirebaseAuthResponse
- 正确:
Failure应该是 domain entities 子文件夹中指定的类(请参阅 Failures)。
Failures
- 应存储在
domain/model/failures中。 - 表示特定操作失败的特定域实体。Failure 应包含:
类型原因String toString()方法。DisplayableFailure displayableFailure()方法(能够显示说明失败原因的错误对话框很有用)。
示例
enum VerifyPasscodeFailureType {
Unknown,
ValidationError,
}
class SomeFailure {
// ignore: avoid_field_initializers_in_const_classes
const SomeFailure.unknown([this.cause]) : type = SomeFailureType.Unknown;
const SomeFailure.validationError(PasscodeValidationFailure fail)
: cause = fail,
type = SomeFailureType.ValidationError;
final SomeFailureType type;
final dynamic cause;
DisplayableFailure displayableFailure() {
switch (type) {
case SomeFailureType.Unknown:
return DisplayableFailure(
title: strings.failureTitle,
message: strings.failureMessage,
);
}
}
@override
String toString() {
return 'SomeFailure{type: $type, cause: $cause}';
}
}
Domain entity
- 应存储在
domain/model中。 - 域实体应继承自
Equatable。 - 所有字段都应为 final(以便我们鼓励 不变性)。
- 所有字段都应为非可空,除非 null 值在给定上下文中是有意义的。
示例
class User extends Equatable {
final String username;
const User({
required this.username,
required this.firstName,
required this.lastName,
});
@override
List<Object> get props =>
[
username,
firstName,
lastName,
];
}
Presenter
- 响应用户交互(例如:所有 onTap 方法都会转发给 presenter)。
- 调用 use cases。
- 使用新数据更新 presentation model。
- 使用 cubit 发射状态(
presentationModel)。 - 切勿直接访问 PresentationModel 的 initialParams,而是通过 presentationModel 中的 getter 访问。
- 不存储任何数据,它所需的所有数据都保存在 presentationModel 中。
- presenter 永远不需要
BuildContext,它总是独立于其在 flutter 中使用的事实,因此永远不应依赖于 flutter 类。
ViewModel
- 公开 presentation model 数据到页面的接口。
- 仅包含 getter,视图不应直接修改任何状态。
- 不应公开对 presenter 内部数据,不应由页面直接使用。
PresentationModel
- 存储 presenter 和页面使用的数据。
- 将 InitialParams 作为构造函数参数。
Page
- 切勿直接访问 initialParams。
- 将所有用户交互路由到 presenter,例如:
InkWell(onTap: () => presenter.onTapLogin()。 - 使用
BlocBuilder小部件监听ViewModel中的更改。 - 仅从
ViewModel访问要显示的数据。 - 将 UI 分成更小的部件,提取到单独的文件中。
Json 类
*Json类中的所有字段都应该是可空的。- 所有字段都应该是 final。
- 应包含
toDomain()方法,该方法将 json 转换为域实体。
示例
class UserJson {
UserJson({
required this.username,
required this.firstName,
required this.lastName,
});
factory UserJson.fromJson(Map<String, dynamic> json) =>
UserJson(
username: json['username'] as String? ?? '',
firstName: json['first_name'] as String? ?? '',
lastName: json['last_name'] as String? ?? '',
);
final String? username;
final String? firstName;
final String? lastName;
Balance toBalanceDomain() =>
Balance(
username: username ?? "",
firstName: firstName ?? "",
lastName: lastName ?? "",
);
}
协作指南
以下是一些在处理此项目时需要记住的规则/想法:
新库
在向项目中添加新库之前,请与团队和技术负责人进行协商。重要的是不要用低质量的库污染代码,因为这可能会带来代码漏洞,并使维护更加困难。如果库提供的解决方案是微不足道的,最好将代码托管在项目内部,而不是依赖第三方。
// TODO
