Why I Built This
Every finance app I tried either felt like enterprise software or lacked the features I actually needed. I wanted something in between — a personal tool that could handle multiple wallets, show me clear spending trends, and let me snap receipts on the go. So I built it myself.
Finote was also my deep dive into React Native. I wanted to move beyond web and understand mobile-specific constraints: gesture handling, 60fps animations on lower-end devices, and offline-first data patterns.

How It Works
The app revolves around wallets — you create buckets like Salary, Freelance, or Cash, and log transactions against them. Each transaction can have a category, amount, notes, and an optional receipt photo.
The home screen shows a real-time snapshot: total balance across wallets, recent transactions, and an interactive bar chart (weekly or yearly view) powered by react-native-gifted-charts. You can drill into any wallet to see its individual history and trends.
All data syncs through Firebase — Firestore for structured data, Firebase Auth for login. Receipt images upload to Cloudinary for optimization and CDN delivery, keeping the app lightweight even with heavy media usage.


Key Decisions
Zustand over Context for state management
React Context re-renders the entire tree on any change. For a finance app where the transaction list, wallet balances, and chart data all update independently, Zustand's selector-based subscriptions made a significant difference in keeping the UI snappy.
Optimistic UI for transaction creation
When a user logs a transaction, the local state updates immediately. Firestore syncs in the background. If the sync fails, the state rolls back with a toast notification. This pattern eliminates the perceived latency of network requests and keeps the app feeling instant.
Reanimated for custom animations
The default Animated API couldn't hit 60fps for the custom tab bar and chart transitions I wanted. Reanimated runs animations on the native thread, which made complex gestures and spring-based transitions smooth even on mid-range devices.
Blob uploads instead of base64
My first attempt at receipt uploads sent base64-encoded strings directly. This bloated payloads and blocked the main thread during encoding. Switching to Blob-based FormData uploads cut transfer sizes and made uploads non-blocking, keeping the app responsive during file transfers.
What I Learned
Mobile data density requires careful design. Rendering financial data on small screens is a constant tradeoff between granularity and readability. I customized Gifted Charts extensively to ensure touch targets stayed accessible and animations held at 60fps even with yearly datasets.
Offline-first thinking changes your architecture. Even though Finote isn't fully offline-capable, designing with optimistic updates and background sync forced me to think about state consistency in ways that web development rarely does.
React Native has its own performance culture. Bridge calls, JS thread blocking, and re-render cascades are problems you don't encounter on the web. Profiling with Flipper and understanding the Reanimated worklet model were essential skills I picked up.
