Initial Flutter project structure、
This commit is contained in:
@@ -0,0 +1,409 @@
|
||||
# Flutter 项目如何写 Android 应用
|
||||
|
||||
Flutter 不是“Web 项目”或“Android 项目”分开的。当前项目是一个跨平台 Flutter App,只是当前调试时默认选中了 Web。要写 Android 应用,主要还是写 `lib/` 里的 Flutter 代码,然后用 Android 设备或模拟器运行。
|
||||
|
||||
## 核心结论
|
||||
|
||||
写 Android App 主要还是写这里:
|
||||
|
||||
```text
|
||||
lib/
|
||||
main.dart
|
||||
app/
|
||||
features/
|
||||
shared/
|
||||
```
|
||||
|
||||
不是主要写:
|
||||
|
||||
```text
|
||||
android/
|
||||
```
|
||||
|
||||
`android/` 是 Android 原生壳工程,只有在需要权限、包名、签名、原生 SDK 时才改。
|
||||
|
||||
## 为什么默认打开是 Web
|
||||
|
||||
因为当前环境里 Flutter 可用的设备可能优先显示了 Web,或者 IDE 选择的运行设备是 Chrome / Web。
|
||||
|
||||
可以用这个命令看当前可用设备:
|
||||
|
||||
```bash
|
||||
flutter devices
|
||||
```
|
||||
|
||||
如果看到类似:
|
||||
|
||||
```text
|
||||
Chrome (web)
|
||||
Linux (desktop)
|
||||
Android SDK built for x86
|
||||
```
|
||||
|
||||
说明 Flutter 可以选择不同平台运行。
|
||||
|
||||
运行 Web 是:
|
||||
|
||||
```bash
|
||||
flutter run -d chrome
|
||||
```
|
||||
|
||||
运行 Android 是:
|
||||
|
||||
```bash
|
||||
flutter run -d <android-device-id>
|
||||
```
|
||||
|
||||
## 要运行 Android 需要准备什么
|
||||
|
||||
### 1. Android SDK
|
||||
|
||||
当前环境里已经有 Android SDK:
|
||||
|
||||
```text
|
||||
Android SDK version 37.0.0
|
||||
```
|
||||
|
||||
这个没问题。
|
||||
|
||||
### 2. Android 模拟器或真机
|
||||
|
||||
需要至少有一个 Android 设备。
|
||||
|
||||
检查:
|
||||
|
||||
```bash
|
||||
flutter devices
|
||||
```
|
||||
|
||||
如果没有 Android 设备,需要:
|
||||
|
||||
- 打开 Android Studio
|
||||
- 启动 Android Emulator
|
||||
- 或者连接 Android 手机并打开 USB 调试
|
||||
|
||||
## 用真机运行 Android
|
||||
|
||||
手机上操作:
|
||||
|
||||
1. 打开开发者选项
|
||||
2. 打开 USB 调试
|
||||
3. 用 USB 连接电脑
|
||||
4. 手机弹出授权时点允许
|
||||
|
||||
然后电脑运行:
|
||||
|
||||
```bash
|
||||
flutter devices
|
||||
```
|
||||
|
||||
如果看到类似:
|
||||
|
||||
```text
|
||||
xxx device • android-arm64 • Android xx
|
||||
```
|
||||
|
||||
就可以运行:
|
||||
|
||||
```bash
|
||||
flutter run -d xxx
|
||||
```
|
||||
|
||||
如果只有一个 Android 设备,也可以:
|
||||
|
||||
```bash
|
||||
flutter run
|
||||
```
|
||||
|
||||
## 用模拟器运行 Android
|
||||
|
||||
如果使用 Android Studio:
|
||||
|
||||
1. 打开 Android Studio
|
||||
2. 打开 Device Manager
|
||||
3. 创建一个 Android Virtual Device
|
||||
4. 启动模拟器
|
||||
5. 回到项目目录运行:
|
||||
|
||||
```bash
|
||||
flutter devices
|
||||
flutter run
|
||||
```
|
||||
|
||||
或者指定设备:
|
||||
|
||||
```bash
|
||||
flutter run -d emulator-5554
|
||||
```
|
||||
|
||||
## Android 应用代码写哪里
|
||||
|
||||
Flutter 页面、业务逻辑、状态管理还是写在:
|
||||
|
||||
```text
|
||||
lib/
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
lib/
|
||||
main.dart
|
||||
app/
|
||||
app.dart
|
||||
theme.dart
|
||||
features/
|
||||
home/
|
||||
home_page.dart
|
||||
```
|
||||
|
||||
Android 和 Web 会共用这套 Dart 代码。
|
||||
|
||||
也就是说,写:
|
||||
|
||||
```dart
|
||||
Scaffold(
|
||||
appBar: AppBar(title: const Text('首页')),
|
||||
body: const Center(child: Text('这是 Android App')),
|
||||
)
|
||||
```
|
||||
|
||||
它既可以跑在 Android,也可以跑在 Web。只是最终运行目标不同。
|
||||
|
||||
## 什么时候才需要改 `android/`
|
||||
|
||||
平时不要一开始就去 `android/` 写代码。
|
||||
|
||||
只有这些情况需要改:
|
||||
|
||||
### 1. 改 Android 包名
|
||||
|
||||
比如从默认包名改成:
|
||||
|
||||
```text
|
||||
com.yourcompany.patbond
|
||||
```
|
||||
|
||||
会涉及:
|
||||
|
||||
```text
|
||||
android/app/build.gradle.kts
|
||||
android/app/src/main/AndroidManifest.xml
|
||||
android/app/src/main/kotlin/...
|
||||
```
|
||||
|
||||
### 2. 加 Android 权限
|
||||
|
||||
比如相机、相册、定位、网络、蓝牙。
|
||||
|
||||
一般改:
|
||||
|
||||
```text
|
||||
android/app/src/main/AndroidManifest.xml
|
||||
```
|
||||
|
||||
例如网络权限:
|
||||
|
||||
```xml
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
```
|
||||
|
||||
定位权限:
|
||||
|
||||
```xml
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
```
|
||||
|
||||
### 3. 配置应用图标和启动页
|
||||
|
||||
常见会用插件:
|
||||
|
||||
```text
|
||||
flutter_launcher_icons
|
||||
flutter_native_splash
|
||||
```
|
||||
|
||||
### 4. 配置签名打包
|
||||
|
||||
发布 APK / AAB 时需要改 Android 签名配置。
|
||||
|
||||
常见文件:
|
||||
|
||||
```text
|
||||
android/key.properties
|
||||
android/app/build.gradle.kts
|
||||
```
|
||||
|
||||
### 5. 接入原生 SDK
|
||||
|
||||
比如:
|
||||
|
||||
- 微信登录
|
||||
- 支付宝支付
|
||||
- 高德地图
|
||||
- 极光推送
|
||||
- Firebase
|
||||
- 原生蓝牙能力
|
||||
|
||||
## 现在应该怎么开始写 Android App
|
||||
|
||||
建议顺序:
|
||||
|
||||
1. 先把运行设备切到 Android
|
||||
|
||||
```bash
|
||||
flutter devices
|
||||
```
|
||||
|
||||
然后选择 Android 设备运行:
|
||||
|
||||
```bash
|
||||
flutter run -d 设备ID
|
||||
```
|
||||
|
||||
2. 不要改 `android/`,先写 `lib/`
|
||||
|
||||
把默认计数器 Demo 替换成自己的首页。
|
||||
|
||||
3. 按这个结构开始:
|
||||
|
||||
```text
|
||||
lib/
|
||||
main.dart
|
||||
app/
|
||||
app.dart
|
||||
theme.dart
|
||||
features/
|
||||
home/
|
||||
home_page.dart
|
||||
```
|
||||
|
||||
4. 如果后面需要接口,再加:
|
||||
|
||||
```text
|
||||
lib/core/network/
|
||||
```
|
||||
|
||||
5. 如果需要登录,再加:
|
||||
|
||||
```text
|
||||
lib/features/auth/
|
||||
```
|
||||
|
||||
6. 如果需要 Android 权限,再去改:
|
||||
|
||||
```text
|
||||
android/app/src/main/AndroidManifest.xml
|
||||
```
|
||||
|
||||
## Android 和 Web 写法有什么区别
|
||||
|
||||
大部分 UI 和业务代码一样。
|
||||
|
||||
但是有些能力要注意平台差异:
|
||||
|
||||
```text
|
||||
相机
|
||||
相册
|
||||
定位
|
||||
蓝牙
|
||||
文件系统
|
||||
推送
|
||||
支付
|
||||
扫码
|
||||
后台服务
|
||||
本地通知
|
||||
```
|
||||
|
||||
这些功能在 Android 上通常需要:
|
||||
|
||||
- Flutter 插件
|
||||
- Android 权限
|
||||
- 有时还需要 Android 原生配置
|
||||
|
||||
例如访问相机,可能需要:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
image_picker: ^x.x.x
|
||||
```
|
||||
|
||||
然后 AndroidManifest 里配置权限。
|
||||
|
||||
## 如何确认现在运行的是 Android
|
||||
|
||||
运行成功后,终端会显示类似:
|
||||
|
||||
```text
|
||||
Launching lib/main.dart on Android SDK built for x86 in debug mode...
|
||||
```
|
||||
|
||||
如果是 Web,则会显示:
|
||||
|
||||
```text
|
||||
Launching lib/main.dart on Chrome in debug mode...
|
||||
```
|
||||
|
||||
所以看这一行就知道当前跑在哪个平台。
|
||||
|
||||
## 常用命令
|
||||
|
||||
查看设备:
|
||||
|
||||
```bash
|
||||
flutter devices
|
||||
```
|
||||
|
||||
运行到 Android:
|
||||
|
||||
```bash
|
||||
flutter run -d <android-device-id>
|
||||
```
|
||||
|
||||
运行到 Chrome:
|
||||
|
||||
```bash
|
||||
flutter run -d chrome
|
||||
```
|
||||
|
||||
构建 Android APK:
|
||||
|
||||
```bash
|
||||
flutter build apk
|
||||
```
|
||||
|
||||
构建 Android App Bundle,发布 Google Play 用:
|
||||
|
||||
```bash
|
||||
flutter build appbundle
|
||||
```
|
||||
|
||||
清理构建缓存:
|
||||
|
||||
```bash
|
||||
flutter clean
|
||||
```
|
||||
|
||||
重新拉依赖:
|
||||
|
||||
```bash
|
||||
flutter pub get
|
||||
```
|
||||
|
||||
## 建议先做这一步
|
||||
|
||||
先运行:
|
||||
|
||||
```bash
|
||||
flutter devices
|
||||
```
|
||||
|
||||
如果能看到 Android 设备,就用它的 ID 运行:
|
||||
|
||||
```bash
|
||||
flutter run -d 设备ID
|
||||
```
|
||||
|
||||
如果看不到 Android 设备,那不是代码问题,是还没有连接真机或启动模拟器。
|
||||
|
||||
代码开发上,继续写 `lib/` 即可。Flutter 的 Android App 本质上就是这套 Dart UI 跑在 Android 容器里。
|
||||
@@ -0,0 +1,802 @@
|
||||
# 常见 App 项目结构
|
||||
|
||||
Flutter 项目里,真正主要写代码的是 `lib/`。一个常见、可维护的 App 结构通常不是按“页面类型”乱放,而是按“应用层级 + 功能模块”来组织。
|
||||
|
||||
推荐结构大概是这样:
|
||||
|
||||
```text
|
||||
lib/
|
||||
main.dart
|
||||
|
||||
app/
|
||||
app.dart
|
||||
router.dart
|
||||
theme.dart
|
||||
|
||||
core/
|
||||
config/
|
||||
constants/
|
||||
network/
|
||||
storage/
|
||||
errors/
|
||||
utils/
|
||||
|
||||
features/
|
||||
auth/
|
||||
data/
|
||||
domain/
|
||||
presentation/
|
||||
|
||||
home/
|
||||
data/
|
||||
domain/
|
||||
presentation/
|
||||
|
||||
profile/
|
||||
data/
|
||||
domain/
|
||||
presentation/
|
||||
|
||||
shared/
|
||||
widgets/
|
||||
models/
|
||||
extensions/
|
||||
|
||||
generated/
|
||||
```
|
||||
|
||||
这是一种比较适合中大型 App 的结构。小项目可以简化。
|
||||
|
||||
## `main.dart`
|
||||
|
||||
应用入口。
|
||||
|
||||
通常只做很少的事情:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
runApp(const App());
|
||||
}
|
||||
```
|
||||
|
||||
如果项目复杂一点,可能还会做:
|
||||
|
||||
- 初始化 Flutter 绑定
|
||||
- 初始化本地存储
|
||||
- 初始化日志
|
||||
- 初始化 Firebase
|
||||
- 初始化依赖注入
|
||||
- 捕获全局异常
|
||||
|
||||
例如:
|
||||
|
||||
```dart
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
runApp(const App());
|
||||
}
|
||||
```
|
||||
|
||||
原则是:`main.dart` 不应该写页面 UI,也不应该堆业务逻辑。
|
||||
|
||||
## `app/`
|
||||
|
||||
放整个 App 级别的配置。
|
||||
|
||||
常见内容:
|
||||
|
||||
```text
|
||||
app/
|
||||
app.dart
|
||||
router.dart
|
||||
theme.dart
|
||||
```
|
||||
|
||||
### `app.dart`
|
||||
|
||||
放根组件,一般是 `MaterialApp` 或 `MaterialApp.router`。
|
||||
|
||||
例如:
|
||||
|
||||
```dart
|
||||
class App extends StatelessWidget {
|
||||
const App({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Patbond',
|
||||
theme: AppTheme.light,
|
||||
home: const HomePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `router.dart`
|
||||
|
||||
放路由配置。
|
||||
|
||||
如果页面少,可以先不用这个文件。
|
||||
|
||||
如果后面页面多,建议用 `go_router`,比如:
|
||||
|
||||
```text
|
||||
/login
|
||||
/home
|
||||
/profile
|
||||
/settings
|
||||
```
|
||||
|
||||
### `theme.dart`
|
||||
|
||||
放主题配置,例如:
|
||||
|
||||
- 主色
|
||||
- 字体
|
||||
- 按钮样式
|
||||
- AppBar 样式
|
||||
- 输入框样式
|
||||
- 明暗主题
|
||||
|
||||
## `core/`
|
||||
|
||||
放“全局基础能力”。
|
||||
|
||||
这些东西通常不属于某个具体页面或业务模块,而是整个 App 都可能用。
|
||||
|
||||
常见结构:
|
||||
|
||||
```text
|
||||
core/
|
||||
config/
|
||||
constants/
|
||||
network/
|
||||
storage/
|
||||
errors/
|
||||
utils/
|
||||
```
|
||||
|
||||
### `core/config/`
|
||||
|
||||
放环境配置,例如:
|
||||
|
||||
```text
|
||||
dev
|
||||
test
|
||||
prod
|
||||
```
|
||||
|
||||
比如:
|
||||
|
||||
```dart
|
||||
class AppConfig {
|
||||
static const apiBaseUrl = 'https://api.example.com';
|
||||
}
|
||||
```
|
||||
|
||||
### `core/constants/`
|
||||
|
||||
放全局常量,例如:
|
||||
|
||||
```dart
|
||||
class AppConstants {
|
||||
static const appName = 'Patbond';
|
||||
}
|
||||
```
|
||||
|
||||
可以包括:
|
||||
|
||||
- App 名称
|
||||
- 默认分页大小
|
||||
- 缓存 key
|
||||
- 接口超时时间
|
||||
- 正则表达式
|
||||
|
||||
### `core/network/`
|
||||
|
||||
放网络请求相关代码,例如:
|
||||
|
||||
```text
|
||||
network/
|
||||
api_client.dart
|
||||
api_response.dart
|
||||
interceptors.dart
|
||||
```
|
||||
|
||||
如果用 `dio`,通常会在这里封装:
|
||||
|
||||
- baseUrl
|
||||
- 请求头
|
||||
- token
|
||||
- 错误处理
|
||||
- 超时
|
||||
- 日志拦截器
|
||||
|
||||
### `core/storage/`
|
||||
|
||||
放本地存储封装,例如:
|
||||
|
||||
- SharedPreferences
|
||||
- SecureStorage
|
||||
- Hive
|
||||
- SQLite
|
||||
|
||||
比如:
|
||||
|
||||
```text
|
||||
storage/
|
||||
local_storage.dart
|
||||
secure_storage.dart
|
||||
```
|
||||
|
||||
### `core/errors/`
|
||||
|
||||
放错误类型和异常处理。
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
errors/
|
||||
app_exception.dart
|
||||
failure.dart
|
||||
```
|
||||
|
||||
### `core/utils/`
|
||||
|
||||
放通用工具方法。
|
||||
|
||||
例如:
|
||||
|
||||
- 日期格式化
|
||||
- 金额格式化
|
||||
- 防抖
|
||||
- 校验函数
|
||||
- 文件大小转换
|
||||
|
||||
注意:不要把业务逻辑都塞进 `utils/`,否则它会变成垃圾桶目录。
|
||||
|
||||
## `features/`
|
||||
|
||||
这是业务模块目录,也是实际开发最常动的地方。
|
||||
|
||||
一个功能一个文件夹,比如:
|
||||
|
||||
```text
|
||||
features/
|
||||
auth/
|
||||
home/
|
||||
profile/
|
||||
order/
|
||||
settings/
|
||||
```
|
||||
|
||||
例如登录注册相关都放在:
|
||||
|
||||
```text
|
||||
features/auth/
|
||||
```
|
||||
|
||||
首页相关放在:
|
||||
|
||||
```text
|
||||
features/home/
|
||||
```
|
||||
|
||||
个人资料放在:
|
||||
|
||||
```text
|
||||
features/profile/
|
||||
```
|
||||
|
||||
## Feature 内部结构
|
||||
|
||||
中大型项目里,一个 feature 常见会分成三层:
|
||||
|
||||
```text
|
||||
features/auth/
|
||||
data/
|
||||
domain/
|
||||
presentation/
|
||||
```
|
||||
|
||||
这套结构接近 Clean Architecture,但不一定要一开始就完整照搬。
|
||||
|
||||
### `data/`
|
||||
|
||||
负责数据来源。
|
||||
|
||||
常见内容:
|
||||
|
||||
```text
|
||||
data/
|
||||
models/
|
||||
repositories/
|
||||
services/
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
features/auth/data/
|
||||
models/
|
||||
user_model.dart
|
||||
login_request.dart
|
||||
services/
|
||||
auth_api_service.dart
|
||||
repositories/
|
||||
auth_repository_impl.dart
|
||||
```
|
||||
|
||||
这里一般处理:
|
||||
|
||||
- API 请求
|
||||
- JSON 解析
|
||||
- 本地缓存
|
||||
- 数据模型转换
|
||||
|
||||
例如 `UserModel.fromJson()` 通常就在 `data/models/`。
|
||||
|
||||
### `domain/`
|
||||
|
||||
负责业务规则。
|
||||
|
||||
常见内容:
|
||||
|
||||
```text
|
||||
domain/
|
||||
entities/
|
||||
repositories/
|
||||
usecases/
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
features/auth/domain/
|
||||
entities/
|
||||
user.dart
|
||||
repositories/
|
||||
auth_repository.dart
|
||||
usecases/
|
||||
login.dart
|
||||
logout.dart
|
||||
```
|
||||
|
||||
`domain` 理论上不关心数据来自哪里。
|
||||
|
||||
它只关心业务是什么。
|
||||
|
||||
例如:
|
||||
|
||||
```dart
|
||||
abstract class AuthRepository {
|
||||
Future<User> login(String email, String password);
|
||||
}
|
||||
```
|
||||
|
||||
然后 `data` 层去实现它。
|
||||
|
||||
小项目可以先不拆 `domain/`,直接用 `data + presentation` 就够了。
|
||||
|
||||
### `presentation/`
|
||||
|
||||
负责 UI 和页面状态。
|
||||
|
||||
常见内容:
|
||||
|
||||
```text
|
||||
presentation/
|
||||
pages/
|
||||
widgets/
|
||||
controllers/
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
features/auth/presentation/
|
||||
pages/
|
||||
login_page.dart
|
||||
register_page.dart
|
||||
widgets/
|
||||
login_form.dart
|
||||
controllers/
|
||||
login_controller.dart
|
||||
```
|
||||
|
||||
这里一般放:
|
||||
|
||||
- 页面
|
||||
- 组件
|
||||
- ViewModel / Controller / Bloc / Provider
|
||||
- 页面状态类
|
||||
|
||||
如果用 Riverpod,可能是:
|
||||
|
||||
```text
|
||||
login_controller.dart
|
||||
auth_provider.dart
|
||||
```
|
||||
|
||||
如果用 Bloc,可能是:
|
||||
|
||||
```text
|
||||
auth_bloc.dart
|
||||
auth_event.dart
|
||||
auth_state.dart
|
||||
```
|
||||
|
||||
## `shared/`
|
||||
|
||||
放跨模块复用的 UI 或类型。
|
||||
|
||||
常见结构:
|
||||
|
||||
```text
|
||||
shared/
|
||||
widgets/
|
||||
models/
|
||||
extensions/
|
||||
```
|
||||
|
||||
### `shared/widgets/`
|
||||
|
||||
通用组件,例如:
|
||||
|
||||
```text
|
||||
primary_button.dart
|
||||
empty_view.dart
|
||||
loading_view.dart
|
||||
app_text_field.dart
|
||||
network_image_view.dart
|
||||
```
|
||||
|
||||
这些组件不属于某个具体业务模块,多个页面都能用。
|
||||
|
||||
### `shared/models/`
|
||||
|
||||
通用模型,例如:
|
||||
|
||||
```text
|
||||
pagination.dart
|
||||
option_item.dart
|
||||
result.dart
|
||||
```
|
||||
|
||||
### `shared/extensions/`
|
||||
|
||||
Dart 扩展方法,例如:
|
||||
|
||||
```dart
|
||||
extension StringX on String {
|
||||
bool get isBlank => trim().isEmpty;
|
||||
}
|
||||
```
|
||||
|
||||
## `generated/`
|
||||
|
||||
放自动生成代码。
|
||||
|
||||
例如:
|
||||
|
||||
- 国际化生成文件
|
||||
- 路由生成文件
|
||||
- JSON 序列化生成文件
|
||||
- Assets 生成文件
|
||||
|
||||
这个目录通常不要手写业务逻辑。
|
||||
|
||||
## 资源目录
|
||||
|
||||
Flutter 资源通常不放在 `lib/` 里,而是放在项目根目录。
|
||||
|
||||
常见结构:
|
||||
|
||||
```text
|
||||
assets/
|
||||
images/
|
||||
icons/
|
||||
fonts/
|
||||
lottie/
|
||||
```
|
||||
|
||||
然后在 `pubspec.yaml` 注册:
|
||||
|
||||
```yaml
|
||||
flutter:
|
||||
assets:
|
||||
- assets/images/
|
||||
- assets/icons/
|
||||
- assets/lottie/
|
||||
|
||||
fonts:
|
||||
- family: Inter
|
||||
fonts:
|
||||
- asset: assets/fonts/Inter-Regular.ttf
|
||||
- asset: assets/fonts/Inter-Bold.ttf
|
||||
weight: 700
|
||||
```
|
||||
|
||||
## 测试目录
|
||||
|
||||
常见测试结构:
|
||||
|
||||
```text
|
||||
test/
|
||||
features/
|
||||
auth/
|
||||
login_controller_test.dart
|
||||
core/
|
||||
network/
|
||||
api_client_test.dart
|
||||
```
|
||||
|
||||
测试可以按 `lib/` 的结构对应放。
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
lib/features/auth/presentation/controllers/login_controller.dart
|
||||
```
|
||||
|
||||
对应测试:
|
||||
|
||||
```text
|
||||
test/features/auth/presentation/controllers/login_controller_test.dart
|
||||
```
|
||||
|
||||
## 小项目推荐结构
|
||||
|
||||
如果项目刚开始,不建议一上来就搞得太复杂。
|
||||
|
||||
当前项目可以先用这个结构:
|
||||
|
||||
```text
|
||||
lib/
|
||||
main.dart
|
||||
|
||||
app/
|
||||
app.dart
|
||||
theme.dart
|
||||
|
||||
features/
|
||||
home/
|
||||
home_page.dart
|
||||
|
||||
shared/
|
||||
widgets/
|
||||
```
|
||||
|
||||
这就够了。
|
||||
|
||||
等有登录、接口、缓存、状态管理之后,再扩展成:
|
||||
|
||||
```text
|
||||
lib/
|
||||
main.dart
|
||||
|
||||
app/
|
||||
app.dart
|
||||
router.dart
|
||||
theme.dart
|
||||
|
||||
core/
|
||||
network/
|
||||
storage/
|
||||
constants/
|
||||
utils/
|
||||
|
||||
features/
|
||||
auth/
|
||||
data/
|
||||
presentation/
|
||||
|
||||
home/
|
||||
presentation/
|
||||
|
||||
profile/
|
||||
data/
|
||||
presentation/
|
||||
|
||||
shared/
|
||||
widgets/
|
||||
extensions/
|
||||
```
|
||||
|
||||
## 不建议一开始这样做
|
||||
|
||||
不建议刚开始就建一堆空目录:
|
||||
|
||||
```text
|
||||
core/
|
||||
errors/
|
||||
failures/
|
||||
usecases/
|
||||
validators/
|
||||
services/
|
||||
managers/
|
||||
helpers/
|
||||
providers/
|
||||
mixins/
|
||||
```
|
||||
|
||||
这样看起来很专业,但实际开发时容易变成:
|
||||
|
||||
- 不知道代码该放哪
|
||||
- 文件很多但内容很少
|
||||
- 过度抽象
|
||||
- 后期维护成本高
|
||||
|
||||
最好的做法是:随着需求出现再拆。
|
||||
|
||||
## 常见页面模块示例
|
||||
|
||||
假设要做一个带登录、首页、个人中心的 App,可以这样组织:
|
||||
|
||||
```text
|
||||
lib/
|
||||
main.dart
|
||||
|
||||
app/
|
||||
app.dart
|
||||
router.dart
|
||||
theme.dart
|
||||
|
||||
core/
|
||||
network/
|
||||
api_client.dart
|
||||
storage/
|
||||
token_storage.dart
|
||||
constants/
|
||||
app_constants.dart
|
||||
|
||||
features/
|
||||
auth/
|
||||
data/
|
||||
auth_api.dart
|
||||
auth_repository.dart
|
||||
presentation/
|
||||
login_page.dart
|
||||
register_page.dart
|
||||
auth_controller.dart
|
||||
|
||||
home/
|
||||
presentation/
|
||||
home_page.dart
|
||||
home_controller.dart
|
||||
widgets/
|
||||
home_card.dart
|
||||
|
||||
profile/
|
||||
data/
|
||||
profile_api.dart
|
||||
profile_repository.dart
|
||||
presentation/
|
||||
profile_page.dart
|
||||
edit_profile_page.dart
|
||||
|
||||
shared/
|
||||
widgets/
|
||||
primary_button.dart
|
||||
app_text_field.dart
|
||||
loading_view.dart
|
||||
empty_view.dart
|
||||
```
|
||||
|
||||
## 常见开发流程
|
||||
|
||||
一般做 App 时,顺序可以是:
|
||||
|
||||
1. 搭好 `main.dart` 和 `app/app.dart`
|
||||
2. 建立主题 `app/theme.dart`
|
||||
3. 创建首页 `features/home/home_page.dart`
|
||||
4. 如果有多页面,再加 `app/router.dart`
|
||||
5. 如果有接口,再加 `core/network/`
|
||||
6. 如果有登录 token,再加 `core/storage/`
|
||||
7. 每做一个功能,就在 `features/` 下新建一个模块
|
||||
8. 多个模块共用的组件,再提到 `shared/widgets/`
|
||||
|
||||
## 放代码时的判断标准
|
||||
|
||||
页面放哪里:
|
||||
|
||||
```text
|
||||
features/模块名/presentation/
|
||||
```
|
||||
|
||||
例如登录页:
|
||||
|
||||
```text
|
||||
features/auth/presentation/login_page.dart
|
||||
```
|
||||
|
||||
接口请求放哪里:
|
||||
|
||||
```text
|
||||
features/模块名/data/
|
||||
```
|
||||
|
||||
例如登录接口:
|
||||
|
||||
```text
|
||||
features/auth/data/auth_api.dart
|
||||
```
|
||||
|
||||
多个模块共用的网络封装放哪里:
|
||||
|
||||
```text
|
||||
core/network/
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
core/network/api_client.dart
|
||||
```
|
||||
|
||||
多个页面共用的按钮放哪里:
|
||||
|
||||
```text
|
||||
shared/widgets/
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
shared/widgets/primary_button.dart
|
||||
```
|
||||
|
||||
App 主题放哪里:
|
||||
|
||||
```text
|
||||
app/theme.dart
|
||||
```
|
||||
|
||||
路由放哪里:
|
||||
|
||||
```text
|
||||
app/router.dart
|
||||
```
|
||||
|
||||
常量放哪里:
|
||||
|
||||
```text
|
||||
core/constants/
|
||||
```
|
||||
|
||||
图片放哪里:
|
||||
|
||||
```text
|
||||
assets/images/
|
||||
```
|
||||
|
||||
## 对当前项目的建议
|
||||
|
||||
现在最适合从这个结构开始:
|
||||
|
||||
```text
|
||||
lib/
|
||||
main.dart
|
||||
app/
|
||||
app.dart
|
||||
theme.dart
|
||||
features/
|
||||
home/
|
||||
home_page.dart
|
||||
shared/
|
||||
widgets/
|
||||
```
|
||||
|
||||
等开始做真实功能,比如登录、个人中心、业务列表,再扩展成:
|
||||
|
||||
```text
|
||||
features/
|
||||
auth/
|
||||
profile/
|
||||
bond/
|
||||
settings/
|
||||
```
|
||||
|
||||
如果只是练手或做 MVP,不要过早引入完整 Clean Architecture。先保持简单,把页面跑起来、流程走通,再根据复杂度拆分。
|
||||
@@ -0,0 +1,582 @@
|
||||
# `main.dart`、`app.dart` 和 `const App()` 说明
|
||||
|
||||
## `main.dart` 和 `app.dart` 应该放什么
|
||||
|
||||
以常见 Flutter App 项目结构来看,`main.dart` 和 `app.dart` 的职责应该分得很清楚:
|
||||
|
||||
- `main.dart`:负责“启动前准备”和“启动 App”
|
||||
- `app.dart`:负责“描述整个 App 长什么样、用什么主题、从哪个页面开始、怎么路由”
|
||||
|
||||
## `main.dart` 应该放什么
|
||||
|
||||
`main.dart` 是程序入口,应该尽量薄。
|
||||
|
||||
最基础版本:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'app/app.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const App());
|
||||
}
|
||||
```
|
||||
|
||||
如果项目刚开始,这样就够了。
|
||||
|
||||
## 稍微正式一点的 `main.dart`
|
||||
|
||||
如果后面需要初始化一些东西,可以这样:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'app/app.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
runApp(const App());
|
||||
}
|
||||
```
|
||||
|
||||
`WidgetsFlutterBinding.ensureInitialized()` 的作用是:在 `runApp` 之前确保 Flutter 框架初始化完成。
|
||||
|
||||
什么时候需要它:
|
||||
|
||||
- 初始化 `SharedPreferences`
|
||||
- 初始化本地数据库
|
||||
- 初始化 Firebase
|
||||
- 加载 `.env`
|
||||
- 初始化本地存储
|
||||
- 设置屏幕方向
|
||||
- 初始化依赖注入
|
||||
|
||||
例如以后可能会变成:
|
||||
|
||||
```dart
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await AppStorage.init();
|
||||
await Firebase.initializeApp();
|
||||
|
||||
runApp(const App());
|
||||
}
|
||||
```
|
||||
|
||||
但现在不需要提前写这些。
|
||||
|
||||
## `main.dart` 不应该放什么
|
||||
|
||||
不建议在 `main.dart` 里放:
|
||||
|
||||
- 页面 UI
|
||||
- `Scaffold`
|
||||
- `AppBar`
|
||||
- 首页布局
|
||||
- 登录逻辑
|
||||
- 网络请求
|
||||
- 路由表
|
||||
- 大量主题配置
|
||||
- 业务状态管理
|
||||
|
||||
也就是说,不要让 `main.dart` 变成这样:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
runApp(MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(...),
|
||||
body: ...,
|
||||
),
|
||||
));
|
||||
}
|
||||
```
|
||||
|
||||
这只适合临时 Demo,不适合项目长期维护。
|
||||
|
||||
## `app.dart` 应该放什么
|
||||
|
||||
`app.dart` 放根组件,也就是整个应用壳子。
|
||||
|
||||
最基础版本:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../features/home/home_page.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
class App extends StatelessWidget {
|
||||
const App({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Patbond',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: AppTheme.light,
|
||||
home: const HomePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里负责:
|
||||
|
||||
- App 标题
|
||||
- 是否显示右上角 debug 标识
|
||||
- 全局主题
|
||||
- 首页入口
|
||||
- 路由配置
|
||||
- 本地化配置
|
||||
- 全局导航配置
|
||||
|
||||
## 如果暂时没有路由系统
|
||||
|
||||
项目刚开始,页面少,可以这样:
|
||||
|
||||
```dart
|
||||
return MaterialApp(
|
||||
title: 'Patbond',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: AppTheme.light,
|
||||
home: const HomePage(),
|
||||
);
|
||||
```
|
||||
|
||||
这种最简单。
|
||||
|
||||
适合:
|
||||
|
||||
- 只有首页
|
||||
- 页面数量很少
|
||||
- 暂时不需要登录拦截
|
||||
- 暂时不需要深链接
|
||||
- 暂时不需要命名路由
|
||||
|
||||
## 如果使用 Flutter 自带命名路由
|
||||
|
||||
可以这样:
|
||||
|
||||
```dart
|
||||
return MaterialApp(
|
||||
title: 'Patbond',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: AppTheme.light,
|
||||
initialRoute: '/',
|
||||
routes: {
|
||||
'/': (_) => const HomePage(),
|
||||
'/login': (_) => const LoginPage(),
|
||||
'/profile': (_) => const ProfilePage(),
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
适合小到中等项目。
|
||||
|
||||
优点:
|
||||
|
||||
- 简单
|
||||
- 不需要额外依赖
|
||||
- 比 `Navigator.push` 字符串散落各处更统一
|
||||
|
||||
缺点:
|
||||
|
||||
- 登录拦截不够优雅
|
||||
- 嵌套路由不方便
|
||||
- Web URL 管理不如 `go_router`
|
||||
|
||||
## 如果以后使用 `go_router`
|
||||
|
||||
`app.dart` 会变成:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'router.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
class App extends StatelessWidget {
|
||||
const App({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp.router(
|
||||
title: 'Patbond',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: AppTheme.light,
|
||||
routerConfig: appRouter,
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后 `router.dart` 单独管理路由。
|
||||
|
||||
这种适合:
|
||||
|
||||
- 页面多
|
||||
- Web 地址栏需要正常变化
|
||||
- 有登录态跳转
|
||||
- 有底部 Tab
|
||||
- 有深链接
|
||||
- 有权限拦截
|
||||
|
||||
## `app.dart` 不应该放什么
|
||||
|
||||
不建议在 `app.dart` 里放:
|
||||
|
||||
- 某个页面的具体 UI
|
||||
- 首页复杂布局
|
||||
- 登录表单逻辑
|
||||
- 网络请求实现
|
||||
- 数据模型
|
||||
- 业务判断细节
|
||||
- 大量工具函数
|
||||
|
||||
`app.dart` 是应用壳,不是业务页面。
|
||||
|
||||
## 推荐当前项目的版本
|
||||
|
||||
当前项目还很新,建议先用最简单的版本。
|
||||
|
||||
目录:
|
||||
|
||||
```text
|
||||
lib/
|
||||
main.dart
|
||||
app/
|
||||
app.dart
|
||||
theme.dart
|
||||
features/
|
||||
home/
|
||||
home_page.dart
|
||||
```
|
||||
|
||||
`main.dart`:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'app/app.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const App());
|
||||
}
|
||||
```
|
||||
|
||||
`app/app.dart`:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../features/home/home_page.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
class App extends StatelessWidget {
|
||||
const App({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Patbond',
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: AppTheme.light,
|
||||
home: const HomePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`app/theme.dart`:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppTheme {
|
||||
const AppTheme._();
|
||||
|
||||
static ThemeData get light {
|
||||
return ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.deepPurple,
|
||||
),
|
||||
useMaterial3: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`features/home/home_page.dart`:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HomePage extends StatelessWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Text('Patbond'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 职责总结
|
||||
|
||||
`main.dart`:
|
||||
|
||||
```text
|
||||
负责启动
|
||||
```
|
||||
|
||||
应该包含:
|
||||
|
||||
```text
|
||||
runApp
|
||||
启动前初始化
|
||||
全局异常捕获入口
|
||||
```
|
||||
|
||||
不应该包含:
|
||||
|
||||
```text
|
||||
页面 UI
|
||||
业务逻辑
|
||||
路由细节
|
||||
主题细节
|
||||
网络请求
|
||||
```
|
||||
|
||||
`app.dart`:
|
||||
|
||||
```text
|
||||
负责 App 壳子
|
||||
```
|
||||
|
||||
应该包含:
|
||||
|
||||
```text
|
||||
MaterialApp
|
||||
主题配置入口
|
||||
路由配置入口
|
||||
首页入口
|
||||
本地化配置入口
|
||||
```
|
||||
|
||||
不应该包含:
|
||||
|
||||
```text
|
||||
具体页面布局
|
||||
业务逻辑
|
||||
接口请求
|
||||
复杂状态处理
|
||||
```
|
||||
|
||||
`home_page.dart`:
|
||||
|
||||
```text
|
||||
负责首页 UI
|
||||
```
|
||||
|
||||
应该包含:
|
||||
|
||||
```text
|
||||
Scaffold
|
||||
AppBar
|
||||
body
|
||||
首页交互
|
||||
首页状态
|
||||
```
|
||||
|
||||
## 关于 `const App()`
|
||||
|
||||
如果写:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'app/app.dart';
|
||||
|
||||
void main() {
|
||||
runApp(App());
|
||||
}
|
||||
```
|
||||
|
||||
IDE 提示要去掉或加上 `const`,通常是因为 `App` 构造函数是否支持 `const`。
|
||||
|
||||
在 Flutter 里,如果一个 Widget 可以在编译期确定,推荐写成:
|
||||
|
||||
```dart
|
||||
runApp(const App());
|
||||
```
|
||||
|
||||
而不是:
|
||||
|
||||
```dart
|
||||
runApp(App());
|
||||
```
|
||||
|
||||
## 正确写法
|
||||
|
||||
`main.dart`:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'app/app.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const App());
|
||||
}
|
||||
```
|
||||
|
||||
`app/app.dart` 里面要这样写:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class App extends StatelessWidget {
|
||||
const App({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Patbond',
|
||||
debugShowCheckedModeBanner: false,
|
||||
home: const Scaffold(
|
||||
body: Center(
|
||||
child: Text('Patbond'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
关键是这一行:
|
||||
|
||||
```dart
|
||||
const App({super.key});
|
||||
```
|
||||
|
||||
如果 `App` 类里没有 `const` 构造函数,比如是这样:
|
||||
|
||||
```dart
|
||||
class App extends StatelessWidget {
|
||||
App({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(...);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
那 `runApp(const App());` 就不能用,IDE 可能会提示去掉 `const`。
|
||||
|
||||
## 为什么 Flutter 推荐 `const`
|
||||
|
||||
`const` 的好处是:
|
||||
|
||||
- 编译期创建对象
|
||||
- 减少运行时重复创建 Widget
|
||||
- Flutter rebuild 时更容易优化
|
||||
- 代码语义更清楚:这个 Widget 本身没有可变状态
|
||||
|
||||
对于 `StatelessWidget`,只要构造参数也都是可 `const` 的,一般都应该写 `const` 构造函数。
|
||||
|
||||
## 看到“让我去掉 const”的常见原因
|
||||
|
||||
### 原因 1:`App` 构造函数不是 `const`
|
||||
|
||||
错误:
|
||||
|
||||
```dart
|
||||
class App extends StatelessWidget {
|
||||
App({super.key});
|
||||
}
|
||||
```
|
||||
|
||||
正确:
|
||||
|
||||
```dart
|
||||
class App extends StatelessWidget {
|
||||
const App({super.key});
|
||||
}
|
||||
```
|
||||
|
||||
### 原因 2:`App` 里面继承的不是 Widget,或者写法不完整
|
||||
|
||||
正确:
|
||||
|
||||
```dart
|
||||
class App extends StatelessWidget {
|
||||
const App({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 原因 3:`App` 构造函数里有非 const 默认值或初始化逻辑
|
||||
|
||||
例如:
|
||||
|
||||
```dart
|
||||
class App extends StatelessWidget {
|
||||
App({super.key}) {
|
||||
print('init');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这种就不能是 `const` 构造函数。
|
||||
|
||||
### 原因 4:`App` 构造函数接收了运行时对象
|
||||
|
||||
例如:
|
||||
|
||||
```dart
|
||||
class App extends StatelessWidget {
|
||||
final DateTime now;
|
||||
|
||||
const App({super.key, required this.now});
|
||||
}
|
||||
```
|
||||
|
||||
调用时不能这样:
|
||||
|
||||
```dart
|
||||
runApp(const App(now: DateTime.now()));
|
||||
```
|
||||
|
||||
因为 `DateTime.now()` 不是编译期常量。
|
||||
|
||||
## 结论
|
||||
|
||||
优先写成:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
runApp(const App());
|
||||
}
|
||||
```
|
||||
|
||||
并确保 `App` 里面是:
|
||||
|
||||
```dart
|
||||
class App extends StatelessWidget {
|
||||
const App({super.key});
|
||||
}
|
||||
```
|
||||
|
||||
如果这样之后 IDE 还提示去掉 `const`,说明 `App` 构造函数或参数里有运行时内容。
|
||||
@@ -0,0 +1,343 @@
|
||||
# Patbond Flutter 项目结构说明
|
||||
|
||||
## 项目概况
|
||||
|
||||
这是一个标准 Flutter 新项目,项目名是 `patbond_flutter`。目前还没有真正的业务代码,只有 Flutter 模板生成的计数器 Demo。
|
||||
|
||||
核心信息:
|
||||
|
||||
- 入口文件:`lib/main.dart`
|
||||
- 依赖配置:`pubspec.yaml`
|
||||
- 当前依赖很少:只有 Flutter SDK、`cupertino_icons`、`flutter_lints`
|
||||
- 业务结构尚未搭建
|
||||
- 支持的平台包括:Android、iOS、Web、Linux、macOS、Windows
|
||||
|
||||
## 顶层目录说明
|
||||
|
||||
### `lib/`
|
||||
|
||||
这是之后主要写代码的地方。
|
||||
|
||||
当前只有:
|
||||
|
||||
```text
|
||||
lib/main.dart
|
||||
```
|
||||
|
||||
里面是 Flutter 默认的计数器 Demo,包括:
|
||||
|
||||
- `main()`:应用启动入口
|
||||
- `MyApp`:根组件,创建 `MaterialApp`
|
||||
- `MyHomePage`:默认首页
|
||||
- `_MyHomePageState`:计数器状态逻辑
|
||||
|
||||
以后项目的大部分页面、组件、状态管理、接口请求、业务逻辑都会放在 `lib/` 下面。
|
||||
|
||||
### `pubspec.yaml`
|
||||
|
||||
Flutter 项目的核心配置文件。
|
||||
|
||||
它负责:
|
||||
|
||||
- 项目名称
|
||||
- Dart SDK 版本
|
||||
- 第三方依赖
|
||||
- 图片、字体等资源声明
|
||||
- Flutter 配置
|
||||
|
||||
以后添加依赖会改这里,比如:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
dio: ^x.x.x
|
||||
go_router: ^x.x.x
|
||||
flutter_riverpod: ^x.x.x
|
||||
```
|
||||
|
||||
如果要添加图片资源,也会在这里配置:
|
||||
|
||||
```yaml
|
||||
flutter:
|
||||
assets:
|
||||
- assets/images/
|
||||
```
|
||||
|
||||
### `pubspec.lock`
|
||||
|
||||
依赖锁定文件。
|
||||
|
||||
它记录当前项目实际安装的依赖版本。一般不要手动改,由 `flutter pub get` 自动生成和更新。
|
||||
|
||||
### `analysis_options.yaml`
|
||||
|
||||
Dart / Flutter 代码检查规则配置。
|
||||
|
||||
当前使用的是:
|
||||
|
||||
```yaml
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
```
|
||||
|
||||
也就是 Flutter 推荐的默认 lint 规则。
|
||||
|
||||
以后如果团队有代码规范,可以在这里加规则,比如是否强制单引号、是否允许 `print` 等。
|
||||
|
||||
### `test/`
|
||||
|
||||
测试目录。
|
||||
|
||||
当前只有:
|
||||
|
||||
```text
|
||||
test/widget_test.dart
|
||||
```
|
||||
|
||||
这是 Flutter 默认生成的计数器测试,用来测试点击按钮后数字是否增加。
|
||||
|
||||
当删掉默认计数器页面后,这个测试大概率也要同步修改或删除,否则测试会失败。
|
||||
|
||||
### `android/`
|
||||
|
||||
Android 原生工程目录。
|
||||
|
||||
里面是 Android 平台相关配置,例如:
|
||||
|
||||
- 包名
|
||||
- Gradle 配置
|
||||
- AndroidManifest
|
||||
- 权限
|
||||
- 启动图标
|
||||
- 原生插件注册
|
||||
|
||||
平时写 Flutter UI 不需要经常碰它。
|
||||
|
||||
什么时候会改:
|
||||
|
||||
- 修改 Android 应用包名
|
||||
- 添加相机、定位、蓝牙等权限
|
||||
- 配置 Firebase、推送、地图 SDK
|
||||
- 修改 Android 启动页、图标、签名
|
||||
|
||||
### `ios/`
|
||||
|
||||
iOS 原生工程目录。
|
||||
|
||||
里面是 Xcode 工程配置,例如:
|
||||
|
||||
- `Runner.xcworkspace`
|
||||
- `Info.plist`
|
||||
- iOS 权限
|
||||
- Bundle ID
|
||||
- iOS 插件配置
|
||||
|
||||
什么时候会改:
|
||||
|
||||
- 修改 iOS Bundle ID
|
||||
- 添加相机、相册、定位等权限描述
|
||||
- 配置推送、登录、支付、地图
|
||||
- 修改 iOS 启动页、图标、签名
|
||||
|
||||
### `web/`
|
||||
|
||||
Flutter Web 平台目录。
|
||||
|
||||
主要文件:
|
||||
|
||||
- `index.html`
|
||||
- `manifest.json`
|
||||
- `favicon.png`
|
||||
- `icons/`
|
||||
|
||||
如果要把项目跑成 Web 应用,会用到这里。
|
||||
|
||||
什么时候会改:
|
||||
|
||||
- 改网页标题
|
||||
- 改 favicon
|
||||
- 配置 PWA
|
||||
- 添加 Web 端脚本
|
||||
- 配置第三方 Web SDK
|
||||
|
||||
### `linux/`、`macos/`、`windows/`
|
||||
|
||||
桌面端平台目录。
|
||||
|
||||
如果项目只做移动端,这几个目录通常不用管。
|
||||
|
||||
它们的作用类似 `android/` 和 `ios/`,只是对应桌面端平台。
|
||||
|
||||
### `build/`
|
||||
|
||||
构建产物目录。
|
||||
|
||||
由 Flutter 自动生成。
|
||||
|
||||
不要手动改,也不应该提交重要业务代码到这里。
|
||||
|
||||
### `.dart_tool/`
|
||||
|
||||
Dart / Flutter 工具生成的缓存目录。
|
||||
|
||||
不要手动改。
|
||||
|
||||
### `.idea/`、`patbond_flutter.iml`
|
||||
|
||||
JetBrains / Android Studio / IntelliJ 的 IDE 配置文件。
|
||||
|
||||
通常不写业务逻辑。
|
||||
|
||||
### `.metadata`
|
||||
|
||||
Flutter 项目元信息。
|
||||
|
||||
一般不需要手动改。
|
||||
|
||||
### `README.md`
|
||||
|
||||
项目说明文档。
|
||||
|
||||
现在大概率还是模板内容。以后可以写:
|
||||
|
||||
- 项目介绍
|
||||
- 如何运行
|
||||
- 环境要求
|
||||
- 常用命令
|
||||
- 目录结构
|
||||
- 开发规范
|
||||
|
||||
## 应该从哪里开始写
|
||||
|
||||
第一步应该从 `lib/` 开始,不要先改平台目录。
|
||||
|
||||
当前项目还处于默认模板阶段,建议先把 `lib/` 组织起来。
|
||||
|
||||
推荐的最小起步结构:
|
||||
|
||||
```text
|
||||
lib/
|
||||
main.dart
|
||||
app/
|
||||
app.dart
|
||||
router.dart
|
||||
theme.dart
|
||||
features/
|
||||
home/
|
||||
home_page.dart
|
||||
shared/
|
||||
widgets/
|
||||
constants/
|
||||
utils/
|
||||
```
|
||||
|
||||
含义:
|
||||
|
||||
### `main.dart`
|
||||
|
||||
只保留启动逻辑,例如:
|
||||
|
||||
```dart
|
||||
void main() {
|
||||
runApp(const App());
|
||||
}
|
||||
```
|
||||
|
||||
### `app/app.dart`
|
||||
|
||||
放全局 `MaterialApp`,例如主题、路由、全局配置。
|
||||
|
||||
### `app/router.dart`
|
||||
|
||||
如果项目有多个页面,路由放这里。
|
||||
|
||||
前期简单项目可以先不用路由库,直接用 Flutter 自带 `Navigator`。如果页面比较多,可以考虑 `go_router`。
|
||||
|
||||
### `app/theme.dart`
|
||||
|
||||
统一管理颜色、字体、按钮样式等主题配置。
|
||||
|
||||
### `features/`
|
||||
|
||||
按功能模块放业务代码。
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
features/
|
||||
home/
|
||||
auth/
|
||||
profile/
|
||||
bond/
|
||||
settings/
|
||||
```
|
||||
|
||||
每个功能模块下面可以继续放:
|
||||
|
||||
```text
|
||||
pages/
|
||||
widgets/
|
||||
models/
|
||||
services/
|
||||
```
|
||||
|
||||
但一开始不要拆太细,避免目录很多但没内容。
|
||||
|
||||
### `shared/`
|
||||
|
||||
放跨多个功能复用的东西,例如:
|
||||
|
||||
- 通用组件
|
||||
- 常量
|
||||
- 工具函数
|
||||
- 通用样式
|
||||
- 网络层
|
||||
- 本地存储封装
|
||||
|
||||
## 实际开发顺序建议
|
||||
|
||||
1. 先确定产品的第一个页面,例如首页、登录页、仪表盘、列表页。然后把默认计数器 Demo 替换掉。
|
||||
2. 清理 `lib/main.dart`,不要继续在 `main.dart` 里堆所有页面。可以把根 App 和首页拆出去。
|
||||
3. 建立最小目录结构,建议先建 `lib/app/app.dart` 和 `lib/features/home/home_page.dart`,先不要一口气创建很多空目录。
|
||||
4. 确定状态管理方案。如果只是简单页面,先用 `StatefulWidget`;如果会有登录状态、接口数据、多个页面共享状态,建议尽早选一个方案,例如 `riverpod`、`provider`、`bloc`。
|
||||
5. 确定路由方案。页面少可以先用 `Navigator.push`;页面多、需要登录拦截、深链接,建议用 `go_router`。
|
||||
6. 添加资源目录。如果要放图片、图标、字体,可以加 `assets/images/`、`assets/icons/`、`assets/fonts/`,然后在 `pubspec.yaml` 注册。
|
||||
7. 再处理 Android / iOS 配置。只有当需要权限、包名、签名、推送、第三方 SDK 时,才去改 `android/` 和 `ios/`。
|
||||
|
||||
## 当前这个项目最重要的文件
|
||||
|
||||
现在最应该关注这几个:
|
||||
|
||||
```text
|
||||
lib/main.dart
|
||||
pubspec.yaml
|
||||
analysis_options.yaml
|
||||
test/widget_test.dart
|
||||
```
|
||||
|
||||
其中真正开始写业务,入口是:
|
||||
|
||||
```text
|
||||
lib/main.dart
|
||||
```
|
||||
|
||||
但不建议长期把所有代码写在 `main.dart` 里。
|
||||
|
||||
## 建议的下一步
|
||||
|
||||
如果准备正式开始项目,建议先做这件事:
|
||||
|
||||
把默认计数器 Demo 改成干净的应用骨架:
|
||||
|
||||
```text
|
||||
lib/
|
||||
main.dart
|
||||
app/
|
||||
app.dart
|
||||
theme.dart
|
||||
features/
|
||||
home/
|
||||
home_page.dart
|
||||
```
|
||||
|
||||
然后首页先写一个简单的 `HomePage`,后续所有功能都从 `features/` 下面扩展。
|
||||
|
||||
这个项目还很干净,最适合先把结构搭好,再开始写业务页面。
|
||||
Reference in New Issue
Block a user