This document details the final integration of the Data Layer (Room/Repository) with the Presentation Layer (Jetpack Compose/ViewModel) for the Savings Goals feature. The SavingsListScreen Composable serves as the entry point, responsible for initializing dependencies and observing the necessary state flows from the SavingGoalViewModel. The architecture strictly follows the MVVM (Model-View-ViewModel) pattern.
The SavingsListScreen Composable is the root of the Savings Goals view. Its primary responsibility is not rendering UI elements directly, but rather dependency setup and state observation.
The screen handles the creation and scoping of the core dependencies necessary for the application to function.
- Database: The
AppDatabaseinstance is retrieved usingAppDatabase.getDatabase(context)and wrapped inrememberto ensure it's initialized only once. - Repository: The
SavingGoalRepositoryis created, injecting the database'ssavingGoalDao(). It is also wrapped inremember. - ViewModel: The
SavingGoalViewModelis instantiated using the customSavingGoalViewModelFactory. This factory is crucial for injecting the requiredRepositoryinto the ViewModel, adhering to Dependency Inversion Principles (DIP).
The screen collects the continuous data flows from the ViewModel and translates them into Compose's reactive state.
- Goals List:
viewModel.allGoals(StateFlow) is collected using.collectAsState()into the Compose state variablegoalsList. This state holds the entire list ofSavingGoalobjects. - Statistics:
viewModel.totalStats(StateFlow) is collected into the state variablestats.
The use of collectAsState() ensures that whenever the underlying Room database updates (triggering a new emission from the Flow), the UI automatically recomposes (re-renders) with the latest data.
The screen utilizes the Material 3 Scaffold component for a standard layout and structure.
- Top Bar:
GoalListTopBarhandles navigation and primary screen actions. - Content: The main content is delegated to the
SavingsListContentcomposable. This content view receives the necessary data (goalsList) and interaction hooks (viewModel) to render the list and handle user input.
The data flows in a single direction, ensuring predictability and maintainability:
- DB/Repository: The DAO emits changes via Flow.
- ViewModel: The ViewModel uses
stateInto convert the Room Flow into an observable StateFlow, managing the ongoing state. - UI/Compose: The
SavingsListScreencollects the StateFlow usingcollectAsState(). - User Action: User interacts (e.g., adds a goal).
- ViewModel Action:
viewModel.saveGoal()is called, triggering a DB write operation. - Cycle Restart: The DB update triggers Room's Flow, restarting the cycle from Step 1 with the new data.
This strict UDF cycle guarantees that the UI always reflects the current state of the database.
The Analysis Screen is the core reporting module of Financify, providing users with reactive, real-time insights into their spending habits and savings progress over customizable time periods.
The analysis logic is implemented within the AnalysisViewModel, which is responsible for fetching, processing, and presenting financial data reactively using Kotlin Flows and Coroutines.
The central mechanism is the monthlyStats flow, which ensures data consistency and efficiency:
- Customizable Period: The user sets the number of months (
_selectedMonths) they wish to analyze. flatMapLatest: This crucial operator ensures that if the user changes the month selection rapidly (e.g., from 3 months to 6 months), the previous, slower database query is cancelled, and a new query is immediately initiated for the latest selection. This prevents resource waste and avoids displaying stale data.- Data Aggregation: The
computeMonthlyStatsfunction performs the heavy lifting:- It uses
Calendarlogic to group rawTransactionentities byYear-Month. - It calculates the sum of
INCOMEandEXPENSEamounts for each group. - It converts the raw data into a list of structured
MonthlyStatsobjects, ready for charting.
- It uses
| Function | Purpose |
|---|---|
setMonths(Int) |
Updates the flow trigger, initiating a fresh data fetch and calculation. |
computePeriod(Int) |
Calculates the startDate and endDate timestamps (Long) required for the database query. |
monthlyStats |
The final StateFlow that the UI consumes, containing processed and summarized data. |
The screen is built with Jetpack Compose and is divided into two primary tabs, managed by the SegmentedTab component:
This tab focuses on the transaction data fetched by the AnalysisViewModel.
- Total Calculations: It calculates three key metrics from the aggregated
MonthlyStats:- Total Income
- Total Expense
- Net Income (
Total Income - Total Expense)
- Data Visualization: It displays the monthly income and expense figures using a Chart component (using the extension function
.toBarData()). - Alerts and Messaging: The
AnalysisMessageCardprovides personalized feedback based on theNet Income:- Positive Net Income: Displays a congratulatory savings message (Green card).
- Negative Net Income: Displays an alert, specifically listing the months where expenses exceeded income (Red card).
- Call to Action: Includes a button to navigate to the full Transaction List for deeper review.
This tab integrates data from the separate SavingGoalViewModel to show the user's progress on their financial goals, including completion stats and overall funding status.
The data retrieval is handled by the TransactionRepository which calls the TransactionDao using a Room Query:
// Inside TransactionDao
@Query("SELECT * FROM transactions WHERE date BETWEEN :startDate AND :endDate ORDER BY date ASC")
fun getTransactionsBetweenDates(startDate: Long, endDate: Long): Flow<List<Transaction>>