本节摘要:前面几节已经用上了 http、dio、sqflite、Riverpod——数据能力大半来自生态。本节先管好这摊供应链:pubspec 的版本约束怎么写、版本解析怎么工作、如何安全升级;再看数据通道的另一头——接入 Firebase 这类后端即服务,还是自建服务,两条路的成本与适用分界。
轻记账的依赖清单(节选)长这样:
name: light_ledger environment: sdk: ^3.5.0 # 语言版本约束:不能低于 3.5,不能用 4.0 dependencies: flutter: { sdk: flutter } dio: ^5.7.0 # 插入符:允许 5.7.0 到 6.0.0 之下 flutter_riverpod: ^2.5.1 sqflite: ^2.3.3 shared_preferences: ^2.3.2 dev_dependencies: flutter_test: { sdk: flutter } flutter_lints: ^4.0.0 # 只在开发期用,不进发布包
插入符(^)的语义是破坏性版本之外全放开:^5.7.0 允许 5.8.0、5.9.9,不允许 6.0.0。它背后的假设是语义化版本——第二位是小修与新增(可安全升),第一位是破坏性变更(不可自动升)。所以两个纪律:约束别写死(写 5.7.0 精确版本,等于放弃安全的小版本修复);约束也别放太开(两个直接依赖各自要求的传递依赖冲突时,解析器只能报错,越宽的约束越容易撞)。
真正的版本裁决在解析之后写进锁文件——pubspec.lock 记录每个依赖最终解析到的精确版本。团队协作的铁律:锁文件进版本库。它是"我机器上能跑"与"你机器上能跑"的裁判,CI 与同事用的版本必须一致。发布包的依赖审计也以锁文件为准。
日常操作三件套,各有一个安全习惯:
$ flutter pub get # 按锁文件精确还原(新机器、CI、切分支后都先跑) $ flutter pub upgrade --major-versions # 只升兼容范围内版本(不跨大版本) $ flutter pub outdated # 查看哪些包有新大版本、当前约束挡不挡路
升级大版本的稳妥序列:先看该包的变更日志里破坏性改动的迁移说明,再改 pubspec 约束,然后跑一次全量测试(第 6 章的防线在此收利息),绿了再提交锁文件。升级依赖与改业务代码分开提交,出问题时回滚粒度才够细。
插件不是免费的:每个平台插件都会进入打包产物,部分还带初始化成本与权限声明。三条裁剪原则来自真实项目的体积账:
原则一:能用官方dart核心库解决的不引插件。 日期格式化用 intl 还是手写?深拷贝用循环还是引包?多数"顺手引一下"的工具包,代码量不超过它带来的依赖风险。
原则二:功能重叠的包只留一个。 轻记账早期同时引了 http 与 dio(历史原因),清理掉 http 后编译产物与依赖树都瘦了一圈。定期跑一次依赖树审计,问每个直接依赖"删了会怎样"。
原则三:废弃信号早处理。 包超过一年未更新、issue 区堆积关键缺陷、README 声明不再维护——出现这些信号就启动替换调研,别等它堵死 SDK 升级的那天(被单个僵尸包卡住整个工程升级,是社区最常见的翻车现场)。
数据通道的另一头是谁在接?两条主流路线的成本结构完全不同。
后端即服务(Firebase 为代表):认证、数据库、推送、崩溃统计打包提供,Flutter 侧官方插件齐全。接入从配置工程开始:
import 'package:firebase_core/firebase_core.dart'; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); // 各端配置文件由命令行工具生成并放入宿主壳 runApp(const LightLedgerApp()); }
它换来的速度惊人:轻记账的"多设备同步"若用 Firebase 的云数据库做,第 5.3 节手写的同步队列可以省掉一大半——离线缓存与多端合并是它的原生能力。代价 equally 明确:数据主权与迁移成本(数据在人家格式里,迁移要走导出)、按量计费的不确定性(用户量起来后账单可能陡增)、深度定制的天花板(复杂业务逻辑要迁到云函数,调试链路变长)。
自建服务(REST 或 GraphQL):第 5.1、5.2 节的接口就是这条路。它给出完全的控制权——接口形状、计费、数据模型都由你定,GraphQL 还能按需取字段(移动弱网下省流量实打实)。代价是全栈能力要求:服务器、部署、扩容、安全,样样自己扛。
选择判断按三问走:**验证期还是成熟期?**验证期产品要的是一周上线,后端即服务的速度红利压倒一切。**数据敏感度多高?**强合规场景(金融、医疗)的数据主权要求会直接排除托管方案或限定其部署形态。**团队有没有后端人手?**没有后端工程师的移动团队选自建,等于把同步协议、扩容这些深水问题交给最不熟的人。轻记账的答案:验证期用自建轻服务(团队恰有后端),若重做一遍且无后端人手,会在验证期选 Firebase、数据模型刻意保持可导出——把"将来搬家"的门留着。
锁文件冲突怎么解? 两人先后加了依赖,合并时锁文件打架。解法不是手改锁文件(它是生成物),而是合并双方 pubspec 后本地重新跑一次解析,让工具重出锁文件,再整体提交。把"锁文件冲突必须重新解析"写进团队约定,能省掉无数解释。
传递依赖把坏版本带进来怎么办? 解析报告里某个你从没直接引用的包出了问题。用依赖覆盖约束(dependency_overrides)临时钉住版本是止血,根因处理是去看是谁传递引入的、上游有没有更新——覆盖是绷带不是治疗,长期挂着会让下次解析更难。
离线或内网环境怎么还原依赖? 首选团队自建镜像源(pubspec 里配置镜像地址),其次把锁文件与缓存一起归档。CI 环境要显式声明依赖来源,"上次构建怎么过的"不该成为谜题。
混编工程里从原生侧直接拖入 Flutter 产物的旧做法,在多端构建时经常出现"产物是旧的"的灵异现象——根因是构建顺序没把 Flutter 模块的前置构建排进去。务必走官方的模块集成方式,让构建系统接管产物生成顺序,并在 CI 里固化构建顺序脚本。这类问题排查一次的时间,足够把集成方式改对了。
无论选哪条路,签约(或开工)前把这份清单过一遍,能挡住大半的返工:数据导出口径是否明确、有无导出工具与格式文档;计费模型在小流量与放量后的两档报价是否都算过账;认证体系是否支持你们需要的登录方式与注销合规要求;本地开发与持续集成环境能否脱离生产服务独立运行;出了区域合规问题(数据出境、留存期限)责任边界在哪。清单里的每一问都有对应的真实翻车案例,问在前面是成本最低的保险。
数据进出全链路完成。下一章换帽子:作为质检员,给轻记账的性能、内存、体积与正确性过一遍堂。