跪拜 Guibai
← All articles
Flutter

Veri Fin: The Local-First Android Ledger That Says No to Accounts, Ads, and Auto-Recording

By Talyra42 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Veri Fin shows a working local-first finance app can skip cloud sync and ad revenue entirely, and its pattern of keeping AI tools read-only is a direct answer to the privacy concerns of AI-assisted bookkeeping.

Summary

Veri Fin is a free, open-source Android bookkeeping app that keeps every byte of financial data on the device. There is no account, no ad network, and no cloud synchronization. The developer Talyra42, a frontend engineer, built it after existing apps pushed membership features and busy UIs. The app's core decisions are negative ones: refusing ads despite offers, skipping automatic recording because it would require screen, notification, or accessibility permissions, and leaving AI backend configuration to the user so token costs stay out of the project.

On the technical side, the app is Flutter with SQLite as the storage layer, now at schema v19, handling multi-currency by freezing base amounts at entry time. State management uses a simple Controller plus InheritedNotifier rather than a third-party state library. AI tools are strictly read-only, able to query and chart from a snapshot, but never modify ledger entries. The most time-consuming feature was the desktop widget, which runs in a separate Android environment and needed preview rendering across API versions.

After three months and over 1,300 commits, the repository has 167 stars, 21 forks, and 46 closed issues. The project is GPL-3.0 and accepts issues but not pull requests, to keep the codebase aligned with its internal conventions. The author's reflection: AI accelerates coding only when the product direction and constraints are already clear.

Takeaways
— The app has no account, no ads, and no cloud sync, keeping all data local.
— It uses Flutter with SQLite, now at schema v19.
— Multi-currency handling stores the original amount, actual account deduction, and a frozen base amount separately.
— State management is a custom Controller + InheritedNotifier, not a third-party library.
— AI tools are read-only; they can query, summarize, and generate charts but cannot modify ledger entries.
— Automatic recording was skipped because it needs screen, notification, or accessibility permissions.
— Users must bring their own OpenAI-compatible API key; requests go directly from the device.
— Desktop widgets took the most development time, requiring PNG preview generation for Android 12–14 and a script to export 16 preview images on a real device.
— After three months, the repo has 1,300+ commits, 167 stars, 21 forks, 46 closed issues, and 3 open.
— The project uses GPL-3.0 and accepts issues but not pull requests.
Conclusions

The choice to forgo automatic recording despite its convenience highlights how permission-heavy features can conflict with a strict data boundary; the developer traded convenience for a clear line on what the app can access.

The decision to keep state management off-the-shelf and use a simple Controller with InheritedNotifier suggests that a solo project can benefit more from few abstractions than from adopting a popular library.

The developer's refusal to rewrite in React Native despite familiarity points to a pragmatic rule: if a rewrite doesn't change user-visible behavior, it's often not worth the delay.

Concepts & terms
InheritedNotifier
A Flutter widget that combines InheritedWidget with ChangeNotifier, letting a controller propagate changes to all dependent widgets without a third-party state management library. Veri Fin uses it to expose its VeriFinController to the widget tree.
Frozen base currency amount
In multi-currency bookkeeping, the value of a transaction converted into the user's primary currency at the moment of entry, stored as a separate field. It keeps historical records stable when exchange rates change later.
On-device OCR
Optical character recognition performed locally on the device to extract text from a screenshot, so only the text is sent to a configured AI endpoint for creating draft entries, never the image itself.
From the discussion
Featured comments
ngaiwe 1 likes

You've limited AI queries to read-only, and have recognized results become drafts first—that boundary is very clear in the product. I suggest adding a confirmation scenario across ledgers: select an image in ledger A to generate a draft, switch to ledger B while waiting for the AI response, then click save. The draft should always display the target ledger, and after switching, require re-confirmation to avoid the user looking at B's interface and saving the old draft to the wrong place. You could also add this and the scenario where the original ledger is modified after snapshot generation into regression tests, to verify that the interface prompts match the actual written book_id.

Talyra42

That makes sense, learned something.

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗