
做了三个月业余项目我终于把“AI 开发个人记账 App基于 Flutter”这件事彻底趟通了。这篇文章不打算写那种“手把手教你三天做出记账软件”的速成教程而是想把我踩过的坑、验证过的方案、以及怎么让 AI 真正帮上忙而不是帮倒忙的经验一次性说清楚。无论你是刚开始学 Flutter 的新手还是想给记账类 App 加 AI 能力的老手这篇文章应该都能给你一些实际可用的参考。我先交代一下背景这个项目不是公司需求而是我自己日常记账需求太强烈。市面上的记账软件要么广告太多要么数据被绑在云端要么高级功能全要付费所以干脆自己写一个。技术选型上几乎没有犹豫——Flutter跨平台一套代码两端跑UI 自绘性能足够流畅适合做这种工具型 App。真正让我兴奋的点在于整个开发过程大量用了 AI 编程助手来辅助同时 App 内部也集成了 AI 能力实现“一句话记账”和“自动分类”这两个核心体验。可以说这个项目是 AI 辅助开发 AI 功能内嵌的双重实践。1. 为什么做记账 App以及 AI 在里面的两种角色1.1 个人需求与技术选型的碰撞记账这件事听起来很简单但坚持下来特别难。我过去用过的记账软件最烦的就是一笔一笔手动输入打开 App、点新建、选分类、输金额、写备注一套流程下来十几秒一天几笔就烦了。所以我的核心需求非常明确记录动作要足够快最好一句话就能完成数据要能离线用因为账单本身就是隐私我不太想把所有数据都放在别人服务器上。这两个需求直接决定了技术选型。Flutter 的优势是跨平台 本地存储生态成熟一套 Dart 代码可以同时覆盖 Android 和 iOS维护成本比原生双端开发低一个量级。而且 Flutter 的 UI 渲染是自绘的复杂列表滚动也能保持 60fps记账这种列表密集场景完全不会卡。再加上我自己对 Dart 语法比较熟写起来比 Swift/Kotlin 混着写舒服得多。1.2 AI 的第一层角色辅助我写 Flutter 代码这个项目里AI 编程助手承担了大量重复性编码工作。比如页面骨架、数据模型的定义、表单校验这些模式化很强的代码用自然语言描述需求AI 生成的初始版本能达到七成可用度剩下的三成靠我来调参和修正边界情况。我自己的体验是AI 最擅长的是“有明确套路”的代码。举个实际例子我要写一个“根据月份筛选交易记录并按日期分组”的逻辑只需要把表结构和期望的返回格式说清楚AI 生成的 SQL 和 Dart 层处理代码基本可以直接用。但 AI 也有明显的短板——它经常不理解业务语义。比如我让 AI 设计“分类”表时它默认把“餐饮”“交通”做成固定的枚举值这在实际使用中非常不灵活因为每个人的消费习惯不同分类必须支持用户自增。这类问题只能靠人来发现和修正。1.3 AI 的第二层角色App 内的智能记账能力除了辅助开发我还在 App 内部集成了 AI 能力这属于运行时功能。最核心的场景就是“一句话记账”用户输入“昨天中午和同事吃饭花了 86 块”系统要自动解析出金额 86、时间昨天、分类餐饮、备注“和同事吃饭”。这个功能如果自己做自然语言处理工程量巨大但借助大模型 API 或者本地轻量规则引擎实现成本就低很多。我在最终方案里采用了“本地规则优先 大模型兜底”的策略。本地规则负责处理“金额、日期、常用关键词”这些结构化信息的提取能覆盖七成以上的输入场景速度快、离线可用、零成本。只有当本地规则置信度不足时才把文本发送给大模型做进一步解析。这样既保证了响应速度又兼顾了复杂表达的识别效果。2. 项目整体架构与数据模型2.1 架构设计本地优先云端可选记账数据的隐私属性很强所以我选的是“本地优先”架构SQLite 作为主存储所有核心操作都走本地数据库App 完全离线可用。云端同步做成可选模块用户在设置里自己决定是否开启。整体分层我参考了常见的 Clean Architecture 思路但没有搞得那么重UI 层Flutter Widget只负责渲染和交互状态层Riverpod 管理页面状态业务层Repository封装记账、查询、统计等业务逻辑数据层数据库访问和本地文件操作这样的分层带来的直接好处是如果后续要把本地 SQLite 换成服务端 API只需要重写数据层UI 和业务层完全不用动。我在开发过程中用同步功能验证了这个设计的可靠性替换数据层实现时上层代码确实零改动。2.2 本地数据库怎么选SQLite、Hive 还是 IsarFlutter 生态里有几个常见的内嵌数据库方案我做了对比方案类型优点缺点适用场景sqfliteSQLite 封装成熟稳定生态好SQL 灵活需要手写 SQL类型安全差结构化数据、复杂查询driftSQLite 的 ORM类型安全编译期检查支持迁移学习成本稍高中大型项目推荐Hive纯 Key-Value轻量速度快纯 Dart不适合复杂查询缓存、偏好设置Isar纯 Dart 数据库性能强支持复合查询社区相对年轻新项目测试学习最终我选了 sqflite 手写 SQL原因很简单记账查询虽然不复杂但涉及按月聚合、按分类统计这类语句用 SQL 表达最直观。drift 那种类型安全的写法我也尝试过但项目时间有限最终还是回归了更直接的方式。如果你从零开始我更推荐 drift因为编译期检查能帮你提前暴露很多低级错误。2.3 数据模型设计记账核心表结构记账 App 的核心表设计其实不复杂但有几个细节很关键。我的交易表结构大致是这样CREATE TABLE transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT NOT NULL DEFAULT expense, -- income / expense amount INTEGER NOT NULL, -- 金额单位分 category_id INTEGER, -- 外键关联分类表 account_id INTEGER, -- 账户如现金/银行卡 note TEXT DEFAULT , transaction_time INTEGER NOT NULL, -- 记账时间Unix 毫秒 created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ); CREATE INDEX idx_transactions_time ON transactions(transaction_time); CREATE INDEX idx_transactions_category ON transactions(category_id);几个设计要点我得单独说下。金额字段我用的 INTEGER 存“分”而不是浮点数这能完全规避浮点运算导致的精度问题统计求和时也不会出现 0.30000000000000004 这种尴尬事。时间字段统一存 Unix 毫秒时间戳显示时再按用户所在时区格式化否则很容易出现跨时区错位这种隐蔽 bug。另外我建了交易时间索引月度查询基本都在毫秒级返回。2.4 本地与后端同步的策略如果你也想加“本地数据库 后端同步”能力这里有几个我认为值得分享的设计决策。同步的核心思路是以上次同步时间戳为锚点增量拉取本地合并。我在表里加了一个 sync_status 字段标记每条记录的同步状态待上传、已同步、冲突。每次同步时客户端把待上传的变更批量发给服务端服务端回传该时间点之后的其他端变更客户端再做合并。冲突处理我采用的策略很简单以服务端时间为准保留较新的记录较旧的记录写入审计日志而不是直接覆盖或丢弃。这个方案实现起来不复杂但能解决日常使用中 99% 的同步场景。你不需要引入复杂的 CRDT 算法记账数据本来就是单用户多设备几乎不会出现真正的并发冲突。3. 开发环境准备与常见报错解决3.1 Flutter 安装配置与工具链这一节写给刚入门的朋友。Flutter 安装本身不算难但国内环境下有几个值得注意的点。SDK 下载建议直接从官方渠道获取然后配置环境变量。装好后第一步是运行flutter doctor检查环境完整性它会列出 Android SDK、Xcode、Chrome 等各项工具的缺失情况。IDE 方面我用的是 VS Code Flutter 插件组合启动快、内存占用比 Android Studio 低对老机器更友好。不过 Android Studio 也有它的优势自带的模拟器管理、布局预览、性能分析面板都更完善。实际开发中我两个都装了日常写代码用 VS Code需要看布局或跑性能分析时切到 Android Studio。3.2 两个高频构建报错的解决开发过程中我遇到两个网上讨论特别多的构建报错相信你大概率也会碰到必须单独拉出来讲。第一个是 Gradle 相关You are applying Flutters main Gradle plugin imperatively using the apply method.这个报错出现在 Flutter 3.16 之后版本升级老项目时。根因是 Flutter 默认 Gradle 插件从apply method方式迁移到了声明式插件方式。解决办法是修改 Android 工程的settings.gradle和app/build.gradle按照新模板格式引入插件同时把 Gradle 版本和 AGP 版本对应关系确认好。我建议直接对照一个 Flutter 3.x 版本创建的新项目把配置文件 diff 一下哪个文件不一样就改哪里效率最高。第二个是 Windows 桌面端报错unable to find suitable visual studio toolc...这个报错是运行flutter run -d windows时找不到 MSVC 工具链。解决办法是安装 Visual Studio注意一定要勾选“使用 C 的桌面开发”工作负载里面包含 Windows 10 SDK 和 MSVC 编译器。如果你只需要 Android/iOS 开发这个工具链也可以不装但装了后你就能同时调试 Windows 桌面版对某些功能验证会方便很多。3.3 工程创建与项目骨架环境准备好之后创建项目我推荐这种方式flutter create --org com.example personal_finance cd personal_finance flutter run--org参数用来指定包名前缀最好第一次就设置成你自己的域名后面再改包名会牵扯到 Android 的 applicationId 和 iOS 的 Bundle Identifier流程比较麻烦。项目创建好之后我建议先把目录调整为 feature-first 结构而不是默认的 type-first 结构lib/ features/ transactions/ data/ domain/ presentation/ settings/ core/ database/ network/ utils/feature-first 结构的优势在于围绕一个业务功能把相关代码放在一起重构和查找都非常方便。这对于个人项目尤其好用因为你不可能记住几个月前把某个工具函数放在哪个 utils 文件夹里了。4. 核心功能模块实现4.1 记账主流程金额输入与分类选择记账 App 最核心的交互流程就是“添加一笔账”。我实现的方案分三步输入金额 → 选分类 → 补充时间和备注。金额输入框我做得比较特殊聚焦自动弹出数字键盘金额实时转成大写中文预览这在用户体验上非常讨巧。分类选择用了一个 3 列的网格每个分类是一个图标加文字。分类数据不是硬编码的而是存在数据库表里用户可以在“分类管理”页自己新增、编辑和排序。这里有个经验默认分类的图标要挑得足够直观火锅、打车、工资这些高频场景最好一眼就能看到能减少 50% 以上寻找分类的时间成本。表单校验是 AI 生成代码的高发区但也是容易出错的区位。比如金额输入为空、金额为 0、分类未选这些情况AI 生成的处理逻辑往往是弹个 Toast 了事。我自己补了更细致的处理金额为空时输入框标红并提示分类未选时点击保存按钮会轻微抖动提醒体验比弹 Toast 高级不少。4.2 首页账单列表与月度汇总首页设计我参考了主流记账 App 的布局顶部显示本月总支出、总收入、结余中间是分类占比统计下方是按日期分组的账单列表。月度切换通过左右滑动实现每次切换会重新查询数据库并刷新页面。这里有一个技术要点账单列表如果一次把所有数据加载进来数据量大了之后首屏会明显卡顿。我的方案是采用分页加载每页加载 50 条记录滚动到底部自动加载下一页。配合 Flutter 的ListView.builder组件列表项只在可见区域才被构建实测一万条账单数据滚动依然流畅。分组逻辑用 SQL 处理更高效。把交易记录按日期分组一天是一个 sectionsection 内按时间倒序排列然后在这个基础上做分页SELECT * FROM transactions WHERE transaction_time BETWEEN ? AND ? ORDER BY transaction_time DESC LIMIT 50 OFFSET ?;4.3 图表统计报表饼图和柱状图我用的是fl_chart这个第三方库功能足够完善文档也比较清晰。月度统计页展示两块内容分类占比饼图以及近 6 个月支出趋势柱状图。图表这块最大的坑是空数据异常。当某月完全没有支出记录时饼图如果直接传入空数据集fl_chart会直接抛异常。我的处理是在渲染前先判空为空时显示一个专门的空状态页面文案是“本月还没有支出记录去记一笔吧”。另外分类占比的百分比计算要注意四舍五入导致总和不是 100% 的问题这个需要分配余数到占比最大的分类。4.4 AI 智能分类的实现思路这是整个项目里我投入精力最多也是最有意思的功能。一句话记账的核心链路是语音/文本输入 → 实体提取金额、日期、分类→ 结构化写入。实体提取我用了两层方案。第一层是本地规则引擎。我维护了一个关键词映射表比如“饭、餐、吃、火锅、烧烤”映射到餐饮“地铁、打车、加油、停车”映射到交通。命中关键词后再通过正则表达式提取金额比如“花了 86”“86 块”“86 元”这些常见表达。第二层是当本地规则无法确定分类时调用大模型 API 进行语义理解。提示词设计非常关键我最终的方案要求模型返回固定 JSON 格式避免解析困难你是一个记账助手。根据用户输入的文本提取记账信息。 必须返回 JSON格式如下 {amount: 86.0, category: 餐饮, time: 昨天, note: 和同事吃饭} 如果某项无法确定对应字段返回 null。 用户输入{{user_input}}大模型返回 JSON 后再跟本地分类列表做映射匹配。如果返回的分类不在系统分类里就建议用户新建或归入“其他”。这套组合方案实测下来本地规则能覆盖约 70% 的输入大模型兜底后整体识别准确率能到 95% 以上。5. 网络请求封装与调试实战5.1 dio 基础封装和拦截器Dio 是 Flutter 里最常用的网络库功能全面、插件生态好。我在项目里把 dio 封装成单例统一配置了基础 URL、连接超时、接收超时并注册了三个核心拦截器日志拦截器、认证拦截器、错误转换拦截器。日志拦截器会打印请求方法、URL、请求体、响应状态码和响应体排查问题的基础设施。认证拦截器负责给每个请求自动附加认证信息对于我的同步服务来说就是附加用户令牌。错误转换拦截器把各种网络异常、HTTP 错误码统一转换成业务层可识别的异常类型UI 层拿到之后直接展示对应的错误文案而不是把一长串英文堆栈抛给用户。class ApiClient { late final Dio _dio; ApiClient() { _dio Dio(BaseOptions( baseUrl: https://api.example.com, connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 10), )); _dio.interceptors.add(LogInterceptor(responseBody: true)); _dio.interceptors.add(AuthInterceptor()); _dio.interceptors.add(ErrorInterceptor()); } }5.2 Flutter 抓包完整方法和失败排查调试网络请求光靠日志拦截器还不够。日志只能看到应用内部的收发内容如果你想确认 TSL 握手参数、响应头这些底层信息还是要靠抓包工具。我用的抓包工具是 Charles代理模式工作手机和电脑连同一个局域网手机代理指向电脑的 8888 端口即可。Flutter 抓包和原生 App 抓包有个关键区别Dart 的HttpClient默认不信任 Charles 安装的 CA 证书所以你会发现在浏览器或原生 App 里能正常抓包但 Flutter 的 HTTPS 请求直接报“证书验证失败”。解决办法有两个一是开发模式下在代码里临时信任用户证书二是用https://httpbin.org这种明文 HTTP 接口做调试。如果你遇到“App 抓包失败”从这几个方向排查手机和电脑确认在同一局域网防火墙是否拦截了 8888 端口Android 7.0 以上默认不信任用户 CA 证书需要配置network_security_config.xmlApp 是否开启了 SSL Pinning如果开了需要先绕过才能抓到代理配置后没有重启 App有些连接复用会导致新请求不走代理明文 HTTP 流量还有一个坑Android 9.0 以上默认禁止明文流量。如果你要调试本地服务或 HTTP 测试接口必须在AndroidManifest.xml里配置usesCleartextTraffictrue或者配置针对特定域名的network_security_config。iOS 也有类似的 ATS 限制不过个人开发测试时通常直接用开发模式豁免。6. 性能优化与内存治理6.1 isolate 处理重计算Flutter 是单线程模型UI 线程如果执行耗时操作页面就会掉帧甚至卡死。记账 App 里有两个典型的重计算场景一是从超大数据集里做汇总统计二是 CSV 导入导出时的大批量数据解析。我的处理方式是把这类计算丢到 isolate 中执行。Dart 的 isolate 是独立内存空间的并发单元不会阻塞 UI 线程。最简单的用法是使用Isolate.run()这个 API 从 Dart 2.19 开始可用会把传入的函数放在新 isolate 中执行final result await Isolate.run(() { // 这里执行重计算不会卡 UI return computeMonthlySummary(transactions); });有个容易忽略的点isolate 之间传递的数据需要进行拷贝如果你传了一个非常大的对象序列化和拷贝的开销可能会抵消并发带来的收益。所以实际开发中我通常只把必要的最小数据集传给 isolate而不是把整个数据库对象传过去。6.2 列表性能与内存优化记账 App 是列表密集型场景列表流畅度直接决定使用体验。我的几条优化经验如下列表项尽量使用const构造器。Dart 的const构造器会让相同配置的 widget 复用减少重建成本。我在写列表项 widget 时所有不变的元素都标成 const。避免在build方法里做耗时操作。包括数据库查询、JSON 解析、字符串格式化这些都应该提前算好再传给 widget。我踩过的一个坑是在列表项的build里直接格式化日期列表滚动时每帧都在重复执行优化后改成 item 生成时只格式化一次性能提升非常明显。图片资源要做好压缩和缓存。如果记账单支持添加图片凭证列表里用缩略图而不是原图全尺寸图片等到详情页再加载。debugPrint和日志要控制开关。生产环境里如果开着完整日志单是字符串拼接和 I/O 就会消耗不少性能。我的做法是用全局的 log flag 控制release 模式下把日志级别调到 errordebug 模式下才打印完整信息。7. 常见问题速查与避坑指南我在开发中遇到了不少问题这里整理成速查表每个都是实际排过的雷比看文档直接得多问题现象根本原因解决办法flutter 命令找不到SDK 环境变量未配置把 Flutter SDK 的 bin 目录加到 PATHAndroid 构建下载 Gradle 很慢默认源访问慢配置国内镜像仓库或用本地 Gradle 发行版运行 Windows 桌面端提示没有工具链缺少 VS C 桌面开发组件安装 VS Build Tools勾选“使用 C 的桌面开发”apply method Gradle 插件报错Flutter 升级后插件方式变更对照 Flutter 新模板修改 settings.gradle 和 build.gradle本地数据库 数据库表无法更新旧表结构和新字段不匹配使用 sqflite 的 onUpgrade 回调执行 ALTER TABLE 迁移Flutter https 请求被 MITM 阻断默认不信任用户 CA 证书开发阶段配置信任用户证书或使用 HTTP 测试接口Android 9 以上 HTTP 请求直接被拒绝默认禁止明文流量配置 usesCleartextTraffic 或 network_security_config列表快速滚动时闪烁列表项状态重建使用 PageStorageKey避免滚动位置状态丢失图片加载后内存暴涨原图直接渲染使用 cached_network_image并指定缩略图尺寸表格里最有价值的是最后两条。列表闪烁的问题排查了很久最后发现是因为列表项没有稳定的 KeyFlutter 复用了错误的 element 状态加上PageStorageKey之后彻底解决。数据库迁移的问题也值得展开一下。sqflite 的onUpgrade回调只在数据库版本号增加时触发你要保证新版本代码能兼容旧库的数据。我的策略是每做一次表结构调整就把数据库版本 1并在onUpgrade里写对应的迁移 SQL。比如新增一个 account 表onUpgrade: (db, oldVersion, newVersion) async { if (oldVersion 2) { await db.execute( CREATE TABLE account ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, balance INTEGER DEFAULT 0 ) ); } }这个过程最怕的是用户跨多个版本升级。比如用户从版本 1 直接升到版本 3onUpgrade 里的判断要覆盖所有中间版本的变更。我建议每次发布前找一台机器装旧版本数据库再安装新版本验证升级路径是否顺畅。8. 区别于通用教程的实操心得写到最后我再分享几个通用教程不会写、但对实际开发特别有帮助的个人体会。第一个关于 AI 辅助编码。AI 最好的用法不是“把整个页面让 AI 写”而是“每个函数都让 AI 写”。实际操作中我把大任务拆成很多小函数逐个描述需求让 AI 实现然后自己检查逻辑。这样 AI 的出错率大幅下降而且代码的可读性和单元测试覆盖率都明显提高。如果让 AI 一口气生成一个完整页面你大概率会陷入“改它的代码比重写还累”的困境。第二个关于依赖管理。Flutter 的pubspec.yaml依赖不要追求新奇稳定优先。我会先看这个包的最近更新时间、GitHub star 数量和 issue 回复速度。那些长期没人维护的包哪怕功能再强大也不敢用因为你不知道它哪天会和 Flutter SDK 新版本冲突。第三个关于隐私合规。记账数据高度敏感App 内如果要使用大模型 API不要把用户的账单原文直接传给第三方。我最后的方案是先在本地做脱敏处理只把提取后的结构化数据金额、时间、候选分类发出去即使这样也用完后立即删除服务端日志。这个思路对任何涉及个人数据的 AI 功能都适用。第三个体验关于迭代节奏。个人项目的最大风险不是技术难度而是热情消退。我给自己定的规则是每个版本只做一个核心功能做完就发布、就体验、就收集反馈。初期版本只有记账和列表没有图表、没有同步、没有 AI 分类但已经能日常使用了。后面每加一个功能都有动力支撑因为基础体验已经足够好用。如果你也想做一个记账类 App或者想用 Flutter AI 做点自己的工具希望这篇从真实开发中沉淀出来的文章能帮你少踩几个坑。小步快跑、本地优先、AI 辅助但绝不盲信——这套方法论至少在我这个项目里被验证是可靠的。