三个月、1300 多次提交,和一个不上云的 Android 账本
薄荷记账我用了挺久。免费版日常够用,界面也舒服,但有些自定义功能需要开会员。后来试了一木,功能很多,我只是想记一笔账,看到那么多入口有点累。
找了一圈,没有特别合适的。我是做前端的,平时也在学 Flutter,就想着自己写一个。最开始只是给自己用,没打算发布。

后来功能一点点补起来,就开源了。
现在这个 App 叫 Veri Fin,只有 Android 版,没账号、没广告、没云同步,代码开源。
这篇不逐项介绍功能,仓库和 README 里都有。我想写的是,这段时间为什么做成这样,以及我在技术上做了哪些选择。
为什么自己写
我的需求不复杂:数据放在自己手里,功能不要太多,界面干净一点。
一开始是做给自己,后来觉得别人可能也用得上。免费、本地、开源,多一个人用也不会增加什么成本,就发出来了。
我没做的几个功能
账号
没有做账号。
记账数据很私人,每天吃什么、买什么、收入多少,都在里面。放到服务器上,我不太放心。数据一旦交出去,后面会被怎么使用,用户也很难控制。
广告
有广告商联系过,我没接。
这个软件我自己每天都在用,不想打开先看广告。另外,一个靠广告赚钱的记账软件,说自己不会动用户的数据,也很难让人相信。
自动记账
自动记账考虑过,最后没做。
它一般需要屏幕识别、通知监听或者无障碍权限。这些权限能拿到的信息,比记一笔账多得多。我不确定自己能处理好这些风险,所以放弃。
现在的替代方案是截图识账:
图片在本机做 OCR,只把识别出的文字发给用户自己配置的 AI,生成草稿。原图不上传,草稿也要用户确认才保存。
比自动记账麻烦,但数据边界清楚。
AI 后端
AI 没有做自己的后端。
免费软件承担 token 成本不现实,用户越多支出越高。现在由用户配置兼容 OpenAI 的接口,Key 存在本地,请求从设备直接发出。
用户需要自己准备 Key,门槛高一些,但成本可控。
技术选择
我不是 Flutter 专家,也没有做过完整的技术选型。很多决定只有一个标准:以后一个人还能不能维护。
现在的结构大概是这样,页面不直接访问数据库,写操作统一经过 Controller 和 Repository:
结构不复杂,排查问题时比较容易定位。
Flutter
我是前端出身,RN 本来更熟。当时 RN 的环境和依赖问题比较多,加上想学 Flutter,就选了 Flutter。
后来也考虑过用 RN 重写,后面会讲。
Flutter 对我的主要帮助是 UI 统一。间距、圆角、颜色和标题高度定好之后,几十个页面可以按同一套规则实现。
SQLite
数据层最初考虑过 JSON 文件。
后来算了一下,我每天记五六笔,一年大约两千笔,再加上账户、分类、预算和汇率,JSON 每次都要整体读写,数据量上来之后不合适。
所以第一版用了 SQLite。现在 schema 到 v19,迁移、备份、附件和账本隔离都建立在它上面。
交易表大概长这样,只贴了跟多币种有关的字段:
sql
CREATE TABLE entries (
id TEXT PRIMARY KEY,
book_id TEXT NOT NULL,
type TEXT NOT NULL,
amount REAL NOT NULL, -- 原币金额
currency_code TEXT NOT NULL DEFAULT 'CNY', -- 交易币种
account_amount REAL, -- 账户实际扣款
to_account_amount REAL, -- 转账转入实际金额
base_amount REAL NOT NULL DEFAULT 0, -- 冻结的本位币金额
conversion_source TEXT NOT NULL DEFAULT 'legacy',
-- 其余字段略:分类、账户、备注、时间、标签、退款关联......
);
多币种不能只加一个币种字段。原币金额、账户实际扣款和记账时冻结的本位币金额要分开存,否则汇率变化会影响历史数据。
状态管理
状态管理我一开始不熟悉。Provider、Riverpod、Bloc 都听过,但没有实际用过。
项目最初的骨架由 AI 生成,我确认能运行、能读懂之后继续开发。最后使用的是 Controller + InheritedNotifier:
dart
class VeriFinScope extends InheritedNotifier<VeriFinController> {
const VeriFinScope({
super.key,
required VeriFinController controller,
required super.child,
}) : super(notifier: controller);
static VeriFinController of(BuildContext context) {
final scope = context.dependOnInheritedWidgetOfExactType<VeriFinScope>();
assert(scope != null, 'VeriFinScope not found');
return scope!.notifier!;
}
}
结构很简单,状态在哪里、谁修改、什么时候写库,都可以直接查到。
后来增加的测试和规范,主要是为了避免这个简单结构在功能变多后失控。所以没有使用现成的状态管理库,原因并不复杂:一开始不会,后来发现当前方案够用。
界面
第一版界面比较粗糙,参考了薄荷记账,信息放得比较多。
后来研究过液态玻璃,也给底栏做了可选的玻璃样式。但做到后面,还是觉得界面简单一点更好。
现在除了底栏,其他区域都使用纯色背景和实色卡片,不使用模糊、折射或整屏渐变。除了视觉考虑,也因为每增加一种材质,就多一批需要测试的界面状态。
AI
AI 工具全部只读。
dart
abstract class AiQueryTool {
String get name;
String get description;
AiToolSchema get schema;
AiToolResult run(AiToolContext ctx, Map<String, Object?> args);
}
List<AiQueryTool> buildAiQueryTools() => <AiQueryTool>[
const SummaryTool(),
const CategoryRankingTool(),
const QueryTransactionsTool(),
const NetWorthTool(),
const BudgetStatusTool(),
// ...其余只读工具
];
接口没有保存、修改或删除操作,工具只能读取冻结的数据快照,不能访问 Controller。AI 可以查询、统计和生成图表,但不能修改账目。
记账草稿可以生成,保存仍然由用户完成。

桌面小组件
这段时间花时间最多的是桌面小组件,不是 AI。
它表面上是一张卡片,实际运行在 Flutter 之外的 Android 环境。不同启动器和系统版本的表现不同;应用进程被杀后,它还要显示上一次的快照;跨天时"今日支出"要归零;切换账本、语言或深浅色后,数据也要更新。
四个模板、实例配置、选择器预览和刷新时机,每个部分都有不少边界情况。Android 12 到 14 的选择器预览需要提前渲染 PNG,API 35 之后才支持生成预览。我写了一个脚本,在真机上导出 16 张预览图。
它大概有三种状态:
| 状态 | 桌面显示 | 点击后 |
|---|---|---|
| 未配置,或配置的账本已删除 | 一行"点击进入配置",不显示任何金额 | 打开配置页 |
| 已配置,但快照缺失或版本不符 | 一行"打开应用同步数据" | 打开 App |
| 已配置且快照有效 | 指标 + 金额 + 环/曲线 | 打开对应账本 |
这个模块做完之后,我开始补自动化测试。这些情况靠手动点击覆盖不了,今天修好一个启动器,明天可能在另一个系统版本上出问题。项目只有我一个人,CI 和测试承担了大部分回归检查。
关于重写
有一段时间想用 RN 重写。
一方面是前端出身,对 RN 更熟;另一方面是早期代码质量一般,看起来有很多可以改的地方。
后来算了一下时间,也问自己重写能解决什么问题。结论是主要改善我自己的开发体验,用户不会因此多记一笔账。当前代码不漂亮,但功能是稳定的。为了重写让用户等几个月,不合适。
所以没有重写。
AI 与开发
以前我不确定 AI 能不能辅助完成一个完整产品。现在的经验是,可以,但前提是项目有明确的规范。
项目初期的骨架由 AI 搭建,我确认后,后续相当一部分代码也由它完成。为此我写了很多约束:数据结构、页面骨架、组件复用、持久化时机、颜色规则和确认流程。
AI 的执行速度很快,但约束越模糊,生成的代码越容易停留在"能运行"的程度。能不能在几个月后继续维护,是另一回事。
所以关键路径都补了测试:迁移、仓储、备份恢复、小组件布局。三个月 1300 多次提交,其中很多是工具完成的重复工作。
不过 AI 只能写代码,不能替我做产品决定。自动记账要不要做、广告接不接、界面有没有变复杂,这些都需要我自己判断。
三个月之后
三个月,1300 多次提交。写这篇时,仓库有 167 个 star、21 个 fork,46 个 Issue 已关闭,3 个仍开着。
这些数字不算大,但说明确实有人在用,也有人愿意反馈。
开源协议使用 GPL-3.0。代码可以查看、学习和修改,但不希望被闭源打包销售。准确说,GPL 不禁止商业使用,它要求衍生作品同样开源。
不接受 PR,只处理 Issue。项目规范比较多,外部 PR 很难一次对齐。与其在 review 中反复修改,不如把需求说明白,由我按现有规范实现。这个方式不一定适合所有项目,但目前适合我。
没有商业化。广告联系过,没有接;捐赠入口有,但不是主要收入来源。这个项目首先要保证我自己愿意每天使用。
最大的变化是对做产品的理解。
以前觉得独立开发最重要的是执行力,现在觉得写代码正在变得便宜。AI 可以快速完成页面、测试和错误排查,几天就能做出一个可运行的版本。更难的是决定做什么、不做什么,以及什么时候停止。
所谓 IDEA,落到实际,就是一系列具体决定:不做账号,不做广告,不做屏幕监听;用 SQLite 不用 JSON;让用户自己配置 API Key;在想重写的时候停下来。
这些决定组合起来,才是现在这个产品。
如果你也有类似的需求,建议先把它做成自己每天愿意用的工具,不必一开始就考虑产品化。边界想清楚,规范写下来,能自动化的部分尽量自动化。AI 可以提高速度,但方向仍然要自己判断。
关于 Veri Fin
一个完全免费、开源、数据自主的 Android 记账应用。
感谢你的阅读,如果想要体验软件,欢迎下载!如果想要讨论相关技术,提出建议等,也可以提出 Issue 或者在评论区讨论!