Appearance
Flutter 完整项目承接清单
完整承接 Flutter 项目,不是从写第一个页面开始,而是从确认业务边界、平台边界、工程边界开始。页面能跑只是第一步,真正可交付的项目还要能配置环境、联调接口、处理异常、打包发布、排查线上问题,并让后续开发者能继续维护。
这份清单按真实项目推进顺序组织:先判断项目能不能做,再确定架构和目录,再落到页面、接口、状态、测试、发布和线上运维。
接项目先问清楚什么
开始编码前,至少确认这些问题:
| 问题 | 为什么重要 |
|---|---|
| 目标平台 | Android、iOS、Web、桌面能力差异很大,插件和发布链路不同 |
| 核心用户流程 | 决定页面、路由、状态和接口优先级 |
| 账号体系 | 登录、刷新 token、权限、游客模式会影响全局架构 |
| 接口形态 | REST、GraphQL、WebSocket、文件上传、分页和错误码会影响数据层 |
| 离线要求 | 只缓存列表,还是要支持离线创建、编辑、同步 |
| 设备能力 | 相机、定位、蓝牙、推送、支付、分享都涉及原生配置 |
| 多环境 | dev、test、staging、prod 的域名、证书、日志和开关必须隔离 |
| 发布渠道 | 应用商店、企业分发、测试平台、灰度发布的要求不同 |
| 质量要求 | 崩溃率、启动速度、包体大小、弱网体验、低端机流畅度 |
如果这些问题没有答案,项目后面很容易出现“页面写完了,但不能上线”的情况。
从需求拆到页面和数据
拿到需求后,可以按四层拆:
| 层次 | 拆什么 | 例子 |
|---|---|---|
| 业务流程 | 用户从哪里进入、完成什么动作、失败后怎么办 | 登录 -> 待办列表 -> 新建 -> 同步成功 |
| 页面结构 | 需要哪些页面、弹窗、空状态、错误状态 | 登录页、列表页、详情页、编辑页 |
| 数据模型 | 页面需要什么数据,接口返回什么数据,本地保存什么数据 | TaskDto、Task、TaskViewState |
| 异常分支 | 网络失败、权限不足、数据为空、重复提交怎么处理 | 保留缓存、提示重试、禁用按钮 |
一个简单待办项目可以这样拆:
| 功能 | 页面 | 数据 | 状态 |
|---|---|---|---|
| 登录 | 登录页 | 账号、密码、token、用户信息 | 输入中、提交中、成功、失败 |
| 列表 | 待办列表页 | 待办列表、分页游标、筛选条件 | 加载中、成功、空数据、刷新中、加载更多失败 |
| 详情 | 待办详情页 | 待办详情、操作权限 | 加载中、成功、不存在、无权限 |
| 编辑 | 新建/编辑页 | 表单字段、草稿、校验错误 | 未修改、已修改、保存中、保存失败 |
| 离线 | 后台同步 | 待同步队列、失败原因、重试次数 | 等待同步、同步中、冲突、已完成 |
拆到这个程度后,开发顺序会清晰很多:先做页面骨架和假数据,再接接口,再补缓存、错误和发布链路。
推荐工程结构
中小型项目可以先使用特性分组,避免所有页面、模型、接口堆在同一层。
text
lib/
main.dart
app.dart
bootstrap.dart
core/
config/
error/
logging/
network/
storage/
security/
shared/
theme/
widgets/
utils/
features/
auth/
presentation/
application/
data/
domain/
task/
presentation/
application/
data/
domain/几个关键文件的职责:
| 文件或目录 | 职责 |
|---|---|
main.dart | 程序入口,只做最少启动工作 |
bootstrap.dart | 初始化环境、日志、依赖注入、错误捕获 |
app.dart | 根 Widget、主题、路由、国际化入口 |
core/config | 多环境配置、接口域名、功能开关 |
core/network | HTTP Client、拦截器、超时、错误映射 |
core/storage | 安全存储、偏好、本地数据库封装 |
features/*/domain | 业务实体、规则、Repository 抽象 |
features/*/data | DTO、API、缓存、Repository 实现 |
features/*/application | 用例编排、状态管理、页面命令 |
features/*/presentation | Page、Widget、ViewState |
初学者最容易犯的错误,是页面直接调用 HTTP、直接读写本地存储、直接处理 token、直接拼接错误文案。项目小的时候看起来快,功能一多就会变成每个页面都有一套重复逻辑。
启动入口和全局初始化
入口要尽量稳定。推荐把初始化和根组件分开:
dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final appContext = await bootstrap();
runApp(App(appContext: appContext));
}bootstrap 常见职责:
- 加载环境配置,例如接口域名、日志级别、功能开关。
- 初始化本地存储、数据库、崩溃收集、日志系统。
- 配置网络客户端、请求拦截器、token 刷新。
- 注册依赖,例如 Repository、Service、状态对象工厂。
- 捕获 Flutter 和 Dart 异常,把异常接入日志和崩溃平台。
不要在 build 里做这些事。根组件可以因为主题、语言、登录状态变化而重建,但全局初始化应该有明确入口和生命周期。
多环境配置
真实项目至少区分开发、测试和生产环境。常见做法是用 --dart-define 传入环境:
bash
flutter run --dart-define=APP_ENV=dev
flutter build apk --release --dart-define=APP_ENV=prod
flutter build ipa --release --dart-define=APP_ENV=prod配置里通常包含:
| 配置 | 示例 | 注意点 |
|---|---|---|
| 接口域名 | https://api.example.com | 不要把测试域名打进生产包 |
| 日志级别 | debug、info、error | 生产环境避免输出敏感信息 |
| 功能开关 | 是否开启新页面、新流程 | 要有默认值和回滚策略 |
| 第三方 Key | 地图、推送、统计 | 区分平台和环境,避免泄露 |
| 证书策略 | HTTPS、证书锁定 | 谨慎处理测试证书和生产证书 |
配置错误会导致很隐蔽的问题:登录到测试环境、支付走错商户、日志上报到错误项目、生产包打开实验功能。发布前必须有环境核对步骤。
状态管理怎么选
状态管理库不是越复杂越好。先按状态范围判断:
| 状态范围 | 例子 | 适合方式 |
|---|---|---|
| 组件内部 | 开关、Tab、动画进度、输入框焦点 | StatefulWidget、ValueNotifier |
| 页面级 | 列表、详情、表单提交、分页 | Controller、ChangeNotifier、Riverpod、Bloc |
| 跨页面 | 登录用户、购物车、全局配置、权限 | 全局状态容器或依赖注入 |
| 持久化 | token、缓存、用户偏好、草稿 | 本地存储或数据库 |
选型时看三个指标:
- 团队是否能理解和维护。
- 页面状态是否能被清楚表达和测试。
- 异步请求、错误、取消订阅、资源释放是否有统一写法。
初学阶段可以先用 StatefulWidget + Repository 跑通,再逐步引入更清晰的状态对象。项目变大后,不要让一个页面里的状态对象同时负责 UI、接口、缓存、导航和埋点。
页面开发标准
每个页面至少要有这些状态:
| 状态 | 说明 |
|---|---|
| 首次加载 | 进入页面时的数据加载 |
| 成功内容 | 正常展示业务数据 |
| 空数据 | 没有数据时给出下一步动作 |
| 失败 | 展示可理解错误和重试入口 |
| 刷新中 | 保留旧数据,同时显示刷新状态 |
| 提交中 | 防止重复点击,必要时显示进度 |
| 权限不足 | 说明原因,给出返回、申请或登录入口 |
页面代码要避免:
build里发请求。- 页面直接解析 HTTP 状态码。
- 页面直接操作数据库。
- 每个按钮各自写一套防重复提交。
- 表单校验只靠后端错误返回。
- 异常被吞掉后只显示空白。
页面可以先写假数据和 ViewState,等状态分支完整后再接接口。这样页面结构不会被接口细节牵着走。
接口联调和数据层
接口联调不要从页面开始。更稳定的顺序是:
- 确认接口文档、字段类型、错误码、分页方式。
- 定义 DTO,对齐接口 JSON。
- 定义 Entity,表达项目内部稳定模型。
- 定义 Repository 抽象,屏蔽远程和本地数据来源。
- 写 RemoteDataSource 和 LocalDataSource。
- 写错误映射,把网络错误、认证错误、业务错误统一成应用错误。
- 页面只消费 ViewState,不直接依赖接口 JSON。
分页接口要额外确认:
| 问题 | 说明 |
|---|---|
| 分页方式 | page/pageSize、offset/limit、cursor |
| 排序规则 | 新数据插入后旧分页是否稳定 |
| 下拉刷新 | 是否清空旧数据,失败时是否保留旧数据 |
| 加载更多 | 最后一页如何判断,失败是否可重试 |
| 本地缓存 | 缓存第一页还是缓存完整查询条件 |
联调阶段要保存几组典型数据:正常列表、空列表、字段缺失、权限不足、服务错误、超时、分页最后一页。这些数据能用于页面验收和自动化测试。
表单、权限和资源
很多 Flutter 项目不是难在页面展示,而是难在边界场景。
| 场景 | 需要考虑 |
|---|---|
| 表单 | 本地校验、后端校验、重复提交、草稿恢复、键盘遮挡 |
| 文件上传 | 文件大小、格式、进度、失败重试、权限拒绝 |
| 图片 | 加载图、错误图、缓存、内存、裁剪、不同屏幕密度 |
| 权限 | 首次申请、拒绝、永久拒绝、系统设置返回 |
| 推送 | token 注册、登录切换、通知点击路由、权限关闭 |
| 深链 | 冷启动、后台唤醒、参数缺失、无权限页面 |
| 国际化 | 文案、日期、数字、货币、文字长度 |
| 可访问性 | 语义标签、点击区域、字体缩放、颜色对比 |
这些内容最好在项目早期就建立统一封装。等功能写完再补,通常会牵动大量页面。
测试和质量门禁
一个可维护项目至少有三层验证:
| 层级 | 验证什么 |
|---|---|
| 静态检查 | 格式、lint、未使用代码、明显类型问题 |
| 自动化测试 | 业务规则、状态分支、Repository、关键页面 |
| 手工验收 | 真机、弱网、权限、发布包、异常流程 |
常用命令:
bash
dart format --set-exit-if-changed .
flutter analyze
flutter test
flutter test integration_test手工验收不要只走成功路径。至少覆盖:
- 首次安装和升级安装。
- 未登录、登录过期、账号切换。
- 无网络、弱网、接口超时、服务端错误。
- 空数据、长文本、大图片、大列表。
- 权限拒绝、永久拒绝、从系统设置返回。
- Android 返回键、iOS 手势返回、应用切后台再回来。
发布前检查
发布前可以按这张表过一遍:
| 检查项 | Android | iOS |
|---|---|---|
| 应用 ID | applicationId 是否正确 | Bundle ID 是否正确 |
| 版本号 | versionCode、versionName | Build、Version |
| 签名 | keystore、别名、密码、渠道 | 证书、描述文件、Team |
| 权限 | Manifest、运行时权限 | Info.plist 权限文案 |
| 混淆 | R8/ProGuard 规则 | 原生 SDK 配置 |
| 包体 | ABI、资源压缩、图片大小 | 资源、符号配置 |
| 环境 | 生产域名、生产 Key | 生产域名、生产 Key |
| 观测 | 崩溃、日志、性能、埋点 | 崩溃、日志、性能、埋点 |
发布记录建议包含:版本号、Git 提交、构建命令、环境、主要变更、已知问题、回滚方式。没有这些信息,线上出问题时很难判断影响范围。
线上问题闭环
上线后需要能从现象追到原因:
| 现象 | 需要的数据 |
|---|---|
| 崩溃升高 | 版本、设备、系统、堆栈、用户路径 |
| 启动慢 | 冷启动耗时、初始化步骤、插件耗时 |
| 页面白屏 | 路由、接口、状态、异常日志 |
| 接口错误 | 请求 ID、错误码、耗时、重试次数 |
| 卡顿 | 帧耗时、页面、设备、图片和列表数据 |
| 支付/定位/相机异常 | 权限状态、插件版本、系统版本、原生错误 |
线上问题处理顺序:
- 判断影响范围:单用户、单设备、单版本、全量用户。
- 找到可复现路径:页面、账号状态、网络、系统版本。
- 定位责任层:页面状态、数据层、接口、插件、平台配置。
- 制定处理方式:回滚、热修配置、灰度修复、发新版本。
- 补测试和监控:避免同类问题再次出现。
完整承接自检
读完并完成一个练习项目后,应该能独立说明:
- 项目适合 Flutter 的原因和边界。
- 目标平台、环境、接口、权限和发布渠道。
- 目录结构中每一层为什么存在。
- 一个页面从路由进入、加载数据、展示状态、处理错误的完整流程。
- 状态管理选择的理由,以及状态如何释放。
- 接口 DTO、业务 Entity、页面 ViewState 的区别。
- 本地缓存、离线同步、token 存储的边界。
- Android/iOS 权限、签名、构建和发布要检查什么。
- 出现白屏、卡顿、构建失败、线上崩溃时怎么定位。
- 项目交给别人继续维护时,需要留下哪些文档和脚本。
能回答这些问题,才算从“会写 Flutter 页面”进入“能承接 Flutter 项目”的阶段。
