Currency Conversion
Relevant source files
The following files were used as context for generating this wiki page:
- Packages/Services/Sources/Services/ExchangeRate/CurrencyConversionServiceImpl.swift
- Packages/Domain/Sources/Domain/Currency.swift
- Features/ExpensesFeature/Sources/ExpensesFeature/Views/CurrencyList/CurrencyListViewModel.swift
- Packages/Services/Tests/ServicesTests/ExchangeRate/CurrencyConversionServiceImplTests.swift
- Packages/Services/Tests/ServicesTests/SubscriptionSummaryServiceImplTests.swift
- Features/ExpensesFeature/Sources/ExpensesFeature/Views/CurrencyList/CurrencyListView.swift
- Packages/Services/Sources/Services/Formatters/MoneyFormatterImpl.swift
The Currency Conversion system in Trackurr enables the application to handle multiple global currencies for subscription tracking and financial reporting. It provides the infrastructure to fetch real-time exchange rates, cache them for offline use, and perform accurate mathematical conversions between different denominations.
This system is central to providing users with a unified view of their spending. Even when subscriptions are billed in various currencies (e.g., USD, EUR, PLN), the app can aggregate these into a single "Default Currency" for totals, charts, and analytics.
Sources: README.md:27-30, Features/ExpensesFeature/Sources/ExpensesFeature/Views/CurrencyList/CurrencyListView.swift:70-74
Architecture Overview
The currency system is built on a modular architecture that separates domain data, service logic, and user interface components.
The diagram shows the relationship between the domain model, the service implementation, and the UI layer.
Sources: Packages/Domain/Sources/Domain/Currency.swift:10-15, Packages/Services/Sources/Services/ExchangeRate/CurrencyConversionServiceImpl.swift:14-20, Features/ExpensesFeature/Sources/ExpensesFeature/Views/CurrencyList/CurrencyListViewModel.swift:39-50
Domain Model: Currency
The core data structure is the Currency struct, which follows ISO 4217 standards. It stores the currency code and its corresponding symbol.
| Property | Type | Description |
|---|---|---|
code | String | ISO 4217 code (e.g., "USD", "EUR") |
symbol | String | The currency symbol (e.g., "$", "€") |
The system pre-defines a comprehensive list of supported currencies categorized by region, including the Americas, Europe, Asia-Pacific, Middle East, and Africa.
Sources: Packages/Domain/Sources/Domain/Currency.swift:10-143
Currency Conversion Service
The CurrencyConversionServiceImpl manages the logic for converting amounts and maintaining exchange rate data.
Exchange Rate Providers and Fallback Chain
The service implements a fallback chain to ensure reliability. It attempts to fetch rates from multiple providers in order:
- Online Providers: Frankfurter and fawazahmed0 (referenced in documentation/logs).
- Local Cache: Persistent storage in
UserDefaults. - Bundled Rates: A local
exchange_rates.jsonfile used as a final fallback.
This flowchart illustrates the resilient fetch strategy used to maintain up-to-date conversion data.
Sources: Packages/Services/Sources/Services/ExchangeRate/CurrencyConversionServiceImpl.swift:14-17, Packages/Services/Sources/Services/ExchangeRate/CurrencyConversionServiceImpl.swift:76-96
Conversion Logic
Conversion is performed by normalizing values against a base rate. If the source and target currencies are identical, the original amount is returned immediately. Otherwise, the formula is:
Result = (Amount / SourceRate) * TargetRate
public func convert(amount: Decimal, from source: Currency, to target: Currency) -> Decimal {
if source.code == target.code { return amount }
let currentRates = effectiveRates()
guard let sourceRate = currentRates[source.code],
let targetRate = currentRates[target.code],
sourceRate > 0
else {
return amount
}
return (amount / sourceRate) * targetRate
}
Sources: Packages/Services/Sources/Services/ExchangeRate/CurrencyConversionServiceImpl.swift:54-65
Data Persistence and Caching
Exchange rates are cached in UserDefaults to allow for offline functionality. The service tracks the date of the last refresh and the source of the data.
- Cache Key:
exchange_rates_cache - Refresh Policy: Rates are refreshed at most once daily if a network connection is available.
- Storage Format: A dictionary of
[String: Decimal]where the key is the ISO code.
Sources: Packages/Services/Sources/Services/ExchangeRate/CurrencyConversionServiceImpl.swift:31-35, Packages/Services/Sources/Services/ExchangeRate/CurrencyConversionServiceImpl.swift:77-79
User Interface and Integration
The CurrencyListView and CurrencyListViewModel provide the user interface for selecting currencies.
List Modes
The system supports two distinct interaction modes:
| Mode | Description |
|---|---|
defaultCurrency | Used in Settings. Shows info sections and exchange rates relative to the current selection. Persists changes to app preferences. |
currencyPicker | Used in forms (e.g., adding a subscription). Simplified view that returns a selection via a callback. |
Sources: Features/ExpensesFeature/Sources/ExpensesFeature/Views/CurrencyList/CurrencyListViewModel.swift:10-16, Features/ExpensesFeature/Sources/ExpensesFeature/Views/CurrencyList/CurrencyListView.swift:11-20
Exchange Rate Display
When in the defaultCurrency mode, the UI displays the current exchange rate for each currency in the list relative to the user's primary currency.
Financial Reporting Integration
The currency system is heavily utilized by the SubscriptionSummaryService to calculate aggregate metrics across a portfolio of subscriptions that may use diverse currencies.
Summary Functions
spentThisMonth: Converts all billing events that occurred in the current month to the base currency and sums them.monthlyAverage: Normalizes all subscriptions (weekly, yearly, etc.) to a monthly cost in the base currency.lowest/highest: Compares the converted values of all subscriptions to identify the most and least expensive entries in a consistent denomination.
Sources: Packages/Services/Tests/ServicesTests/SubscriptionSummaryServiceImplTests.swift:65-79, Packages/Services/Tests/ServicesTests/SubscriptionSummaryServiceImplTests.swift:165-175
Conclusion
The currency conversion framework provides a robust foundation for Trackurr's financial features. By combining a standardized domain model with a resilient multi-provider service and clean UI abstractions, it ensures that users can accurately track global expenses with minimal friction. The use of bundled fallbacks and persistent caching guarantees that financial totals remain available regardless of the device's connectivity status.