Dependency Injection (Factory)
Relevant source files
The following files were used as context for generating this wiki page:
Trackurr utilizes a centralized Dependency Injection (DI) system built on the Factory library to manage services, stores, and cross-feature dependencies. This architecture ensures that the application is modular, testable, and capable of swapping implementations based on the runtime environment (e.g., Production, Unit Tests, SwiftUI Previews, or UI Tests).
Sources: CLAUDE.md:15-15, Trackurr/Dependencies/Container/AppContainer.swift:15-22
Architecture Overview
The DI system is structured around three primary components: the Container extension for registration definitions, AppContainer for environment-specific implementation mapping, and DependenciesProvider for orchestration during the app launch cycle.
Registration Strategy
Dependencies are registered as Factory properties within an extension of the Container class. Each registration typically defaults to a fatalError to ensure that a concrete implementation is explicitly provided during the configuration phase, preventing silent failures due to missing dependencies.
Sources: Packages/Interfaces/Sources/Interfaces/Container.swift:6-9
The diagram shows how DependenciesProvider routes registration calls to AppContainer based on the active build environment.
Sources: Trackurr/Dependencies/Container/DependenciesProvider.swift:23-44
Environment-Specific Containers
The AppContainer enum serves as the central hub for mapping protocols (interfaces) to their concrete implementations. It supports four distinct environments:
| Environment | Method | Purpose |
|---|---|---|
| Production | registerDependencies() | Uses real services and a persistent ModelContainer. |
| Unit Tests | registerDependenciesForUnitTests() | Uses Mocks and an in-memory ModelContainer. |
| Previews | registerDependenciesForPreviews() | Uses Mocks for stable SwiftUI Preview rendering. |
| UI Tests | registerDependenciesForUITests() | Specifically configured for automation. |
Sources: Trackurr/Dependencies/Container/AppContainer.swift:15-22, Trackurr/Dependencies/Container/AppContainer+UnitTests.swift:14-22, Trackurr/Dependencies/Container/AppContainer+Previews.swift:14-22
Production Registration
In production, AppContainer initializes the ModelContainer for the app and registers concrete implementations found in the Services package.
static func registerDependencies() {
modelContainer = .forApp()
registerStores()
registerServices()
registerLogging()
registerAnalytics()
}
Sources: Trackurr/Dependencies/Container/AppContainer.swift:25-32
Component Categories
Dependencies are logically grouped into Stores, Services, and Infrastructure components.
Stores (Persistence)
Stores manage the lifecycle of SwiftData entities. In production, they are initialized with the mainContext from the AppContainer.modelContainer.
- subscriptionStore: Manages subscription records.
- categoryStore: Manages system and custom categories.
- tagStore: Handles user-defined tags.
- preferencesStore: Manages user settings.
Sources: Trackurr/Dependencies/Container/AppContainer.swift:37-60, Packages/Interfaces/Sources/Interfaces/Container.swift:10-44
Services (Business Logic)
Services implement business logic and are shared across features via protocols defined in the Interfaces package.
| Service | Scope | Implementation Example |
|---|---|---|
appEventBus | .shared | AppEventBusImpl |
logger | .singleton | LoggerImpl |
appAnalytics | .singleton | AppAnalyticsImpl |
purchaseService | .shared | PurchaseServiceImpl |
Sources: Packages/Interfaces/Sources/Interfaces/Container.swift:50-186, Trackurr/Dependencies/Container/AppContainer.swift:65-144
Testing and Mocks
The project relies heavily on Factory's ability to override registrations for testing. The registerDependenciesForUnitTests() method populates the container with Mock objects (e.g., SubscriptionStoreMock, AppAnalyticsMock) to ensure the app can launch in a test harness without resolving production logic or external network calls.
This diagram illustrates the relationship between the DI Container and the different implementations provided by AppContainer.
Sources: Trackurr/Dependencies/Container/AppContainer+UnitTests.swift:25-50, Trackurr/Dependencies/Container/AppContainer.swift:38-40
Launch Orchestration
The DependenciesProvider view wrapper is the entry point for DI configuration. It is initialized at the root of the application and ensures that configureDependencies() is called before the view hierarchy is constructed.
private static func configureDependencies() {
#if DEBUG
if AppProcess.isRunningUITests {
AppContainer.registerDependenciesForUITests()
return
}
// ... (other debug checks)
#endif
configureFirebase()
AppContainer.registerDependencies()
}
Sources: Trackurr/Dependencies/Container/DependenciesProvider.swift:23-44
Dependency Injection via Factory provides the Trackurr project with a compile-safe, modular foundation. By separating interface definitions in Packages/Interfaces from implementations in Packages/Services and configuring them via AppContainer, the architecture maintains a strict separation of concerns that facilitates rapid development and robust testing.