When people look at their personal finances, their perspective changes depending on the question they are asking:
- At 2:00 PM on a Tuesday, they ask: "Did my salary arrive today, and what did I spend on lunch?" (A Daily perspective).
- On the last day of the month, they ask: "Did I save money this month, and why was my restaurant bill so high?" (A Monthly perspective).
- At tax season or on New Year's Eve, they ask: "How did my overall wealth and annual spending trend across the last 12 months?" (A Yearly perspective).
Most finance applications force you into a single fixed view—usually an infinite scrolling list or a rigid monthly calendar.
In FinLedger, we engineered a unified, multi-period ledger architecture that allows users to glide seamlessly between Daily feeds, Monthly summaries, and a comprehensive 12-Month Yearly matrix with zero swipe lag.
Here is how that reactive multi-period data pipeline is built in Jetpack Compose and Kotlin Coroutines.
The Three Temporal Horizons
flowchart TD
subgraph MultiPeriodArchitecture["The Multi-Period Perspective Hierarchy"]
direction TB
Daily["1. Daily View:
Granular audit feed, date headers, daily net burn, sticky C/F balances"]
Monthly["2. Monthly View:
Calendar grouping, category spending breakdown, cash flow comparison"]
Yearly["3. Yearly Matrix:
12-Month tabular grid (Jan - Dec), monthly income vs expense, running balance"]
end
1. The Daily Feed: Micro-Auditability
The Daily view is designed for operational precision.
- Transactions are grouped chronologically with high-contrast sticky day headers.
- Each date header displays the daily net cash flow (e.g., "+45,000" or "-1,850").
- The summary top card displays the exact Carried-Forward (C/F) balance from all prior days, grounding today's spend in real liquidity.
2. The Monthly View: Habit & Budget Analysis
The Monthly view shifts focus from individual transactions to spending habits.
- Groups transactions across the entire calendar month.
- Integrates with category budgets to show real-time burn rates and pacing alerts.
- Displays the monthly cash flow comparison: total operational income vs total operational expenditure.
3. The 12-Month Yearly Matrix: High-Level Solvency
The Yearly view solves the "long-term visibility" problem using a custom 12-month financial matrix table (YearlyMatrixTable.kt).
- Renders a clean 12-row tabular grid from January to December.
- Each row displays three authoritative columns: Income, Expense, and Ending Running Balance.
- Clicking any row in the annual matrix instantly navigates the user directly to that month's detailed transaction feed.
The Unified Data Pipeline: One Flow to Rule Them All
A common architectural mistake is creating separate, independent database queries and ViewModels for each time period: a DailyViewModel, a MonthlyViewModel, and a YearlyViewModel.
That approach leads to duplicate code, divergent calculation logic, and jarring screen transitions.
In FinLedger, all three perspectives are powered by a single reactive coroutine pipeline in LedgerViewModel:
flowchart LR
subgraph Pipeline["Unified Reactive Pipeline"]
direction TB
Inputs["User Inputs:
_selectedPeriod (Daily/Monthly/Yearly)
_currentDate (Epoch Timestamp)
_filterState (Accounts, Categories)
_searchQuery (Debounced)"] --> Combine["Kotlin Flow combine()"]
RoomDB[("Room Database
(All Accounts & Transactions)")] --> Combine
Combine --> Worker["Dispatchers.Default:
Compute C/F Balance, Partition Date Groups, Build 12-Month Matrix"]
Worker --> State["Immutable LedgerUiState"]
State --> UI["Jetpack Compose Screen
(Instant Recomposition)"]
end
- The ViewModel maintains lightweight reactive state holders for
_selectedPeriod(Daily, Monthly, Yearly),_currentDate, and_filterState. - A single
combineoperator merges these parameters with raw database streams from Room. - The transformation pipeline evaluates the active period and offloads all mathematical aggregation to
Dispatchers.Default:- If
Daily: It slices transactions for that specific day and computes prior C/F balance. - If
Monthly: It filters transactions between day 1 and the last day of that month (LocalDate.lengthOfMonth()). - If
Yearly: It evaluates the entire calendar year and constructs the 12-monthYearlyMatrixRowobjects in a single pass.
- If
- The pipeline emits an immutable
LedgerUiStateconsumed directly by the Jetpack Compose user interface viacollectAsStateWithLifecycle.
Smooth Gestures: The Pager Navigation Shell
To switch between time periods and months, the user interface uses a horizontal swipe gesture.
When a user swipes left or right:
- Changing the date simply updates
_currentDatein the ViewModel. - The reactive pipeline recomputes state in under 4 milliseconds on a background thread.
- Jetpack Compose uses structural keys on list items (
items(transactions, key = { it.id })) to animate changes fluidly without re-measuring the entire layout.
Preserving Invariants Across Time Horizons
Regardless of which period the user is viewing, the underlying financial math never drifts:
- Internal transfers contribute exactly 0.00 to monthly and yearly income/expense totals.
- Credit card debt is subtracted from liquid cash across all views to preserve the Available Spendable Money invariant.
- Opening balances remain balance sheet equity snapshots, never leaking into annual revenue summaries.
Key Takeaway
Financial clarity requires adjusting the zoom level without losing mathematical truth.
By powering Daily feeds, Monthly summaries, and 12-Month Yearly matrices through a single, background-computed reactive pipeline, FinLedger delivers seamless, instant transitions across all time horizons without dropped frames or accounting discrepancies.
FinLedger is a privacy-first, offline-first personal finance application for Android that models bank accounts, credit cards, and cash without double-counting.
Explore FinLedger on Google Play: https://play.google.com/store/apps/details?id=com.devanshufinance.app

Top comments (0)