An honest retrospective on building Trya, a personal finance mobile app. From the first commit to production crashes, including architectural choices that seemed obvious... until they weren't.
The starting point: a problem I lived myself
Like many people, I had tried every budget app on the market. They were either too complex, too rigid, or simply not adapted to the way I think about money.
The trigger was a simple observation: there's no single right way to manage your budget. Some people love tracking every euro by category (the classic approach). Others prefer setting broad buckets based on the 50/30/20 rule popularized by Elizabeth Warren: 50% for needs, 30% for wants, 20% for savings.
So I decided to build an app that respects both approaches. Trya was born.
The stack: React Native, Expo, and decisions I don't regret
The stack choice was clear from day one: React Native with Expo. Not to follow hype, but for a very pragmatic reason: I wanted to ship on iOS and Android without duplicating business logic.
Expo Router for file-based navigation: this is the silent revolution of the Expo ecosystem. Every file inside app/ becomes a route. Nested layouts read like a folder tree. No more manually wiring navigation stacks.
app/
(tabs)/
budget/
index.tsx → /budget
category-details/
index.tsx → /budget/category-details
transaction/
index.tsx → /budget/category-details/transaction
accounts/
heritage/
summary/
When I saw the final structure with dozens of nested screens (edit modals, transaction details, category pickers), I realized just how much pain this choice had saved me.
Zustand for state management: I could have gone with Redux. I could have gone with Context. I chose Zustand because it imposes nothing. One store = one file, clear logic, no boilerplate. And when you have multiple stores talking to each other (accounts, transactions, budget, net worth), Zustand's lightness really shines.
The hardest part: modeling two budget methods
This is where the business domain got genuinely interesting.
The classic method looks simple on the surface: track expenses by category, see where money goes. But in practice, you need to handle fixed charges, custom trackers, discrete envelopes, one-off vs recurring expenses. Each feature seemed small individually, but together they created a non-trivial surface of complexity.
The Warren method (50/30/20) adds another layer: you need to map the user's categories onto three groups, compute ratios in real time against declared income, and display visual feedback that makes sense even when data is partial.
The real challenge: both methods must coexist in the same app, share the same transaction data, but present radically different views. The separation between the data layer (Zustand stores) and the presentation layer (feature-based components) turned out to be absolutely critical.
The bugs that stick with you
Every mobile developer has their collection of memorable bugs. Here are a few:
The date input crash. A date picker that worked perfectly in development but crashed in production on certain Android devices. The culprit? Different date string parsing behavior depending on the system locale. Fix: normalize the format on input, never trust native new Date(string).
Future expenses. Letting users log a future fixed charge sounds trivial. In reality, you need to recalculate totals differently depending on whether you're before or after the expense date, without breaking historical charts. An edge case that cost me several iterations.
The design switch. At some point I decided to overhaul the UI. This kind of decision, made late in development, is often a trap. The lesson: when components are properly decoupled from business logic, redesigning becomes surgical. When they're not, it's a disaster.
What Expo Router actually changes
I want to insist on this because it's the technical choice that surprised me most, in a good way.
Modals, for example. In Trya, almost every important user action opens a modal: add a transaction, edit a category, update an account. With Expo Router, a modal is just a file with a modal presentation in the layout. It has its own URL. It's navigable. It's independently testable.
// app/(tabs)/accounts/edit-bank-modal.tsx
// accessible via router.push('/accounts/edit-bank-modal')
// that's it.
It sounds trivial, but it changes how you think about navigation. You stop wiring callbacks between components, you pass parameters through the URL.
Internationalization from day one
A decision I don't regret: setting up i18n from the very first commit, with separate en.json and fr.json files.
Personal finance apps have a particular relationship with language. Number formatting, date conventions, percentage notation all vary. If you wait until the end to internationalize, you end up doing find & replace across dozens of files full of hardcoded strings. The debt is real.
What's left to build (and why that's exciting)
Trya is in production. Users rely on it every day to track their budget, manage their net worth, and plan their savings.
But there's still a lot of ground to cover: smarter recommendation algorithms, more robust multi-device sync, richer visualizations for the wealth tracking features.
What motivates me most is that every new feature reveals something about how people relate to money. It's not just code, it's applied psychology with React components.
Takeaways
If you're thinking about building a mobile app solo, here's what I'd carry forward:
- Choose your stack for the long run, not the hype. Expo + Zustand + TypeScript ages well.
- Model the business domain before touching code. The most painful bugs come from a blurry understanding of the problem.
- Decouple logic from presentation. The day you want to redesign the UI or swap out a budget method, you'll thank yourself.
- Business edge cases are the real hard bugs. Not technical crashes, the cases where logic is almost right.
Trya is available on iOS and Android. If you have questions about the architecture, technical choices, or anything else, comments are open.
Top comments (0)