去年底我想做个记账 App,需求很简单:记一笔、看列表、月底统计。本来以为两三天能搞定,结果断断续续折腾了两周。这篇 app开发教程 不打算从“Hello World”讲起,而是把我真正踩过的坑摊开说,适合已经能跑通基础项目、准备做第一个完整小产品的人。
一、先定技术栈,别在选型上内耗
我最后选了 Flutter,原因很实际:一台电脑同时出 Android 和 iOS,UI 写起来快。如果你只做安卓,Kotlin + Compose 也完全够用;只做 iOS 就 SwiftUI。选型阶段我犯的错是看了太多对比文章,其实对个人项目来说,能让你今晚就动手的那个框架,就是最好的框架。
依赖我只加了三个:sqflite(本地数据库)、provider(状态管理)、intl(日期格式化)。不要一上来就上 Riverpod、Bloc、依赖注入全家桶,小项目会被架构压死。
二、数据模型:先想清楚“一笔账”是什么
我第一版模型是这样的:
class Bill {
final int? id;
final double amount;
final String category;
final DateTime createdAt;
final String note;
}
跑了两天发现两个问题:一是金额用 double 做加减会出现 0.1 + 0.2 = 0.30000000000000004,统计页数字很难看;二是没有“收入/支出”区分,只能靠金额正负判断,读代码时特别绕。
改版后金额统一存“分”的整数,类型单独加一个枚举:
enum BillType { income, expense }
class Bill {
final int? id;
final int amountInCents;
final BillType type;
final String category;
final DateTime createdAt;
final String note;
}
展示时再除以 100。这个改动很小,但后面所有统计逻辑都清爽了。教训是:涉及钱的字段,永远别用浮点数存。
三、本地存储:建表与迁移
用 sqflite 建表时,我把版本号写死成 1,结果后来加字段时忘了写 onUpgrade,老用户升级后直接崩溃。正确的做法是从第一天就把迁移留好口子:
Future<Database> _initDb() async {
final path = join(await getDatabasesPath(), 'bills.db');
return openDatabase(
path,
version: 2,
onCreate: (db, v) async {
await db.execute('''
CREATE TABLE bills(
id INTEGER PRIMARY KEY AUTOINCREMENT,
amount_cents INTEGER NOT NULL,
type INTEGER NOT NULL,
category TEXT NOT NULL,
created_at INTEGER NOT NULL,
note TEXT
)
''');
},
onUpgrade: (db, oldV, newV) async {
if (oldV < 2) {
await db.execute('ALTER TABLE bills ADD COLUMN note TEXT');
}
},
);
}
另外,日期我存的是毫秒时间戳(INTEGER),不是字符串。字符串排序在跨时区时会出幺蛾子,时间戳省心得多。
四、状态管理:别让 UI 直接碰数据库
我最初的写法是在页面里直接 await db.insert(...),然后 setState 重新查一遍。功能能跑,但列表页、统计页、首页各自查一次库,逻辑重复得厉害。
后来抽了一个 BillStore,所有读写都走它,页面只监听变化:
class BillStore extends ChangeNotifier {
List<Bill> _bills = [];
List<Bill> get bills => List.unmodifiable(_bills);
Future<void> load() async {
_bills = await BillDao.instance.queryAll();
notifyListeners();
}
Future<void> add(Bill bill) async {
await BillDao.instance.insert(bill);
await load();
}
int get monthExpense {
final now = DateTime.now();
return _bills
.where((b) => b.type == BillType.expense &&
b.createdAt.year == now.year &&
b.createdAt.month == now.month)
.fold(0, (sum, b) => sum + b.amountInCents);
}
}
统计逻辑放在 store 里用 fold 算,而不是写 SQL 聚合,是因为数据量小(个人记账撑死几千条),内存计算更灵活,改需求也快。等真到几万条再考虑优化,现在别过度设计。
五、几个容易被忽略的细节
- 输入校验:金额输入框要限制只能输数字和小数点,且最多两位小数。我一开始没做,用户能输入
1.2.3,解析直接抛异常。 - 空状态:列表为空时给个引导文案和“记一笔”按钮,比一片白屏体验好太多。
- 删除确认:左滑删除一定要弹确认框,我测试时误删过好几次数据,用户更会。
- 时区:统计“本月”时用本地时间,别用 UTC,否则月初月末会错一天。
六、打包发布前检查清单
第一次打 release 包,我遇到图标没换、应用名还是 flutter_app、签名用的 debug key 这几个低级问题。发布前过一遍:应用名和图标、版本号、签名配置、隐私政策页面(应用商店基本都要)、以及真机上的深色模式表现。
做小产品的核心不是技术多新,而是把一条完整链路走通:数据存得住、界面不崩、能装到别人手机上。走通一次,后面再做第二个就快了。
这个记账 App 现在我自己每天在用,代码量不到两千行。如果你也在做第一个 app,建议把范围砍到最小,先让它跑起来,再谈优化。这篇 app开发教程 里的坑,希望你能少踩几个。
评论 (0)
还没有评论,快来抢沙发吧~