MVVM-C & State Management
Relevant source files
The following files were used as context for generating this wiki page:
- README.md
- CLAUDE.md
- Features/CategoriesFeature/Tests/CategoriesFeatureTests/Views/CategoryForm/CategoryFormViewModelTests.swift
- Features/ExpensesFeature/Tests/ExpensesFeatureTests/Views/SubscriptionList/SubscriptionListViewModelTests.swift
- Features/CategoriesFeature/Sources/CategoriesFeature/Views/CategoryDetail/CategoryDetailView.swift
- Features/SettingsFeature/Sources/SettingsFeature/Views/SettingsRoot/SettingsRootView.swift
Introduction
The Trackurr project utilizes a modern architectural approach combining MVVM (Model-View-ViewModel) with Coordinators (MVVM-C) to manage navigation and complex business logic. The architecture is designed to be modular, utilizing Swift Packages to separate features and shared services. State management is primarily handled through the SwiftUI Observation framework, ensuring a reactive and efficient user interface.
Sources: README.md:52-66, CLAUDE.md:32-37
Architecture Overview
Trackurr follows a clear separation of concerns by isolating UI, business logic, and data persistence. The architecture leverages Factory for dependency injection, allowing for highly testable and decoupled components.
Core Components
| Component | Responsibility |
|---|---|
| View | Composable SwiftUI views that render the UI and observe the ViewModel. |
| ViewModel | Focused on state and effects, handling user interactions and communicating with services. |
| Coordinator | (Implicit in navigation patterns) Manages the flow between views and feature modules. |
| Store/Service | Handles data persistence (SwiftData) and business logic. |
Sources: CLAUDE.md:28-30, README.md:73-86
The following diagram illustrates the high-level data flow and relationship between these architectural layers:
Note: This diagram shows the bidirectional relationship between View and ViewModel, and the dependency of the ViewModel on Services for data access.
ViewModel State Management
ViewModels in Trackurr are responsible for maintaining the UI state and handling asynchronous operations. They utilize the @Observable macro (leveraging the Observation framework) to trigger UI updates automatically.
State Lifecycle and Persistence
ViewModels typically manage state through several phases:
- Initial State: Defined in the initializer or through context objects (e.g.,
CategoryFormContext). - Screen Tracking: Views trigger
onScreenAppear()to log analytics. - Data Loading: Methods like
loadSubscriptions()orloadCategories()fetch data from stores. - Validation: Internal logic (e.g.,
isFormValid) ensures data integrity before persistence. - Persistence: Calling Store methods to create, update, or delete records.
Sources: Features/CategoriesFeature/Tests/CategoriesFeatureTests/Views/CategoryForm/CategoryFormViewModelTests.swift:15-32, Features/ExpensesFeature/Tests/ExpensesFeatureTests/Views/SubscriptionList/SubscriptionListViewModelTests.swift:141-150
Example: Form ViewModel Flow
The CategoryFormViewModel demonstrates how state is managed during an edit or create operation:
Note: The flow highlights the interaction between user input, validation, persistence, and analytics.
Sources: Features/CategoriesFeature/Tests/CategoriesFeatureTests/Views/CategoryForm/CategoryFormViewModelTests.swift:145-160, Features/CategoriesFeature/Tests/CategoriesFeatureTests/Views/CategoryForm/CategoryFormViewModelTests.swift:245-255
Dependency Injection and Stores
The project uses Factory for centralized Dependency Injection. This allows ViewModels to receive mock services during testing while using real implementations at runtime.
Data Synchronization and Events
Global state changes (like iCloud sync or database seeding) are communicated via an AppEventBus. ViewModels subscribe to specific events to refresh their local state. For example, SubscriptionListViewModel reloads data when it receives a SeedingCompleteEvent.
Sources: Features/ExpensesFeature/Tests/ExpensesFeatureTests/Views/SubscriptionList/SubscriptionListViewModelTests.swift:234-245, Features/SettingsFeature/Sources/SettingsFeature/Views/SettingsRoot/SettingsRootView.swift:125-135
Store Integration Table
The following table describes the primary stores involved in state management:
| Store | Purpose | Primary Methods |
|---|---|---|
| CategoryStore | Manages system and custom categories. | fetchAll(), create(), update(), delete() |
| SubscriptionStore | Manages recurring expense records. | fetchActive(), stop(), resume(), delete() |
| PreferencesStore | Manages user settings (currency, theme). | updateAppearance(), updateDateFormat() |
Sources: Features/CategoriesFeature/Tests/CategoriesFeatureTests/Views/CategoryForm/CategoryFormViewModelTests.swift:425-430, Features/ExpensesFeature/Tests/ExpensesFeatureTests/Views/SubscriptionList/SubscriptionListViewModelTests.swift:131-140, Features/SettingsFeature/Sources/SettingsFeature/Views/SettingsRoot/SettingsRootView.swift:70-85
Navigation and View Coordination
While navigation logic is often embedded within the View layer using NavigationLink and .sheet, it is driven by ViewModel state.
Coordination Pattern
In SettingsRootView, navigation is handled via callbacks provided during initialization. This allows the feature to remain decoupled from the specific navigation structure of the host application.
public init(
onCategoriesTapped: @escaping () -> Void,
onTagsTapped: @escaping () -> Void,
onPaymentMethodsTapped: @escaping () -> Void,
onSubscriptionsTapped: @escaping () -> Void,
onCurrencyTapped: @escaping () -> Void
) { ... }
Sources: Features/SettingsFeature/Sources/SettingsFeature/Views/SettingsRoot/SettingsRootView.swift:16-24
Summary
The MVVM-C and State Management system in Trackurr provides a robust foundation for a modern SwiftUI application. By utilizing the Observation framework for reactive UI, Factory for dependency injection, and a modular package structure, the project ensures high testability and maintainability. Global events and localized storage (SwiftData) are seamlessly integrated to provide a consistent user experience across the app's features.