Subscription & Expense Tracking
Relevant source files
The following files were used as context for generating this wiki page:
- Packages/Domain/Tests/DomainTests/SubscriptionTests.swift
- Features/ExpensesFeature/Sources/ExpensesFeature/Views/SubscriptionForm/SubscriptionFormViewModel.swift
- Features/ExpensesFeature/Sources/ExpensesFeature/Views/SubscriptionList/SubscriptionListView.swift
- Features/ExpensesFeature/Sources/ExpensesFeature/Views/SubscriptionDetail/SubscriptionDetailViewModel.swift
- Packages/Services/Tests/ServicesTests/SubscriptionSummaryServiceImplTests.swift
- TrackurrTests/Persistence/Stores/SubscriptionStoreTests.swift
- README.md
Introduction
The Subscription & Expense Tracking system is the core functional module of the Trackurr application. It is designed to manage recurring financial commitments by allowing users to create, track, and analyze subscriptions across various billing cycles and currencies. The system provides a comprehensive overview of spending habits through localized dashboard views, automated renewal calculations, and detailed expense breakdowns.
This module leverages a modular architecture, utilizing SwiftData for persistence, Factory for dependency injection, and an MVVM pattern with @Observable view models to maintain a reactive and testable codebase. It supports complex billing logic, including weekly, monthly, quarterly, and yearly cycles, while providing real-time currency conversion to a user-defined base currency.
Sources: README.md:14-25, README.md:44-50
Data Architecture & Models
The Subscription Entity
The primary data structure is the Subscription model, which encapsulates all necessary metadata for a recurring expense. It includes properties for financial tracking (amount, currency), scheduling (billing cycle, start date), and categorization (category, tags, icon).
| Field | Type | Description |
|---|---|---|
name | String | The identifier for the subscription (e.g., "Netflix"). |
amount | Decimal | The cost of the subscription per billing cycle. |
currency | Currency | The currency code (USD, EUR, PLN, etc.). |
billingCycle | BillingCycle | Frequency of billing (Daily, Weekly, Monthly, Yearly, etc.). |
startDate | Date | The date the subscription first commenced. |
category | Category | System or custom category for organization. |
isStopped | Bool | Flag indicating if the subscription is currently inactive. |
stopDate | Date? | Optional date when an active subscription was terminated. |
Sources: Packages/Domain/Tests/DomainTests/SubscriptionTests.swift:10-25, TrackurrTests/Persistence/Stores/SubscriptionStoreTests.swift:234-245
Logical Relationships
The system utilizes a relational model to link subscriptions to other domain entities such as Categories, Tags, and Payment Methods.
Subscription Lifecycle Management
Creation and Modification
Subscriptions are managed through the SubscriptionFormViewModel. The creation flow supports pre-filling data from SubscriptionProvider templates (e.g., brand colors and icons for known services like Spotify or Netflix). Validation logic ensures that names are between 1-50 characters and amounts are positive decimals.
State Transitions: Active vs. Stopped
Users can toggle the status of a subscription. Stopping a subscription records a stopDate and halts upcoming renewal calculations. Resuming a subscription clears the stopDate and recalculates the next renewal date based on the original startDate and billingCycle.
Expense Calculation Engine
Currency Conversion
The SubscriptionSummaryService handles financial aggregation. If a subscription's currency differs from the user's baseCurrency (defined in PreferencesStore), the system uses a CurrencyConversionService to normalize the amounts for summary views.
Billing Projections
The system calculates several key metrics to provide financial insights:
- Monthly Equivalent: Normalizes all cycles (e.g., Weekly * 4.33, Yearly / 12) to a standard monthly cost.
- Yearly Total: The projected cost over 12 months.
- Spent This Month: The sum of all renewals that have already occurred within the current calendar month.
- Remaining This Month: Projected upcoming costs for the remainder of the month.
Sources: Features/ExpensesFeature/Sources/ExpensesFeature/Views/SubscriptionDetail/SubscriptionDetailViewModel.swift:108-140, Packages/Services/Tests/ServicesTests/SubscriptionSummaryServiceImplTests.swift:102-140
Summary Logic Sequence
The following sequence illustrates how the SubscriptionListViewModel aggregates data for the dashboard:
User Interface Components
Subscription Dashboard
The SubscriptionListView serves as the primary entry point, featuring:
- Monthly Spending Gauge: A visual representation of spent vs. total monthly budget.
- Compact Metric Cards: Quick views for Active count, Lowest cost, and Highest cost subscriptions.
- Upcoming Carousel: A horizontal scroll of subscriptions renewing soon.
- Alphabetical List: A full list of active subscriptions with swipe actions for quick deletion or stopping.
Detailed Analysis
The SubscriptionDetailView provides a granular look at a single expense, including:
- Payment History: A timeline of past billing dates.
- Spending This Year: A bar chart showing monthly fluctuations in the subscription's cost.
- Billing Info: Breakdowns of monthly equivalents and yearly totals in both original and base currencies.
Sources: Features/ExpensesFeature/Sources/ExpensesFeature/Views/SubscriptionList/SubscriptionListView.swift:70-100, Features/ExpensesFeature/Sources/ExpensesFeature/Views/SubscriptionDetail/SubscriptionDetailView.swift:105-160
Conclusion
The Subscription & Expense Tracking module provides a robust framework for managing recurring costs through strict data modeling and advanced financial services. By integrating currency conversion, flexible billing logic, and automated renewal tracking, the system offers users a high degree of visibility into their financial commitments. The modular design ensures that persistence, business logic, and UI remains decoupled and easily maintainable.
Sources: README.md:52-65, Features/ExpensesFeature/Sources/ExpensesFeature/Views/SubscriptionForm/SubscriptionFormViewModel.swift:250-270