P2: iOS Full DI — all 11 VMs accept dataManager init param

Adds the DI seam to the 5 previously singleton-coupled VMs:
- VerifyEmailViewModel
- RegisterViewModel
- PasswordResetViewModel
- AppleSignInViewModel
- OnboardingTasksViewModel

All now accept init(dataManager: DataManagerObservable = .shared).

iOSApp.swift injects DataManagerObservable.shared at the root via
.environmentObject so descendant views can reach it via @EnvironmentObject
without implicit singleton reads.

Dependencies.swift factories updated to pass DataManager.shared explicitly
into Kotlin VM constructors — SKIE doesn't surface Kotlin default init
parameters as Swift defaults, so every Kotlin VM call-site needs the
explicit argument. Affects makeAuthViewModel, makeResidenceViewModel,
makeTaskViewModel, makeContractorViewModel, makeDocumentViewModel.

Full iOS build green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-04-19 18:47:58 -05:00
parent f0f8dfb68b
commit 42ccbdcbd6
7 changed files with 46 additions and 8 deletions

View File

@@ -40,29 +40,32 @@ final class Dependencies {
// MARK: - Kotlin ViewModel Factories
// SKIE doesn't expose Kotlin default constructor params to Swift, so
// pass DataManager.shared explicitly on each factory.
/// Create a new AuthViewModel instance
func makeAuthViewModel() -> ComposeApp.AuthViewModel {
ComposeApp.AuthViewModel()
ComposeApp.AuthViewModel(dataManager: ComposeApp.DataManager.shared)
}
/// Create a new ResidenceViewModel instance
func makeResidenceViewModel() -> ComposeApp.ResidenceViewModel {
ComposeApp.ResidenceViewModel()
ComposeApp.ResidenceViewModel(dataManager: ComposeApp.DataManager.shared)
}
/// Create a new TaskViewModel instance
func makeTaskViewModel() -> ComposeApp.TaskViewModel {
ComposeApp.TaskViewModel()
ComposeApp.TaskViewModel(dataManager: ComposeApp.DataManager.shared)
}
/// Create a new ContractorViewModel instance
func makeContractorViewModel() -> ComposeApp.ContractorViewModel {
ComposeApp.ContractorViewModel()
ComposeApp.ContractorViewModel(dataManager: ComposeApp.DataManager.shared)
}
/// Create a new DocumentViewModel instance
func makeDocumentViewModel() -> ComposeApp.DocumentViewModel {
ComposeApp.DocumentViewModel()
ComposeApp.DocumentViewModel(dataManager: ComposeApp.DataManager.shared)
}
// MARK: - Service Factories

View File

@@ -13,6 +13,12 @@ class AppleSignInViewModel: ObservableObject {
// MARK: - Private Properties
private let appleSignInManager = AppleSignInManager()
private let dataManager: DataManagerObservable
// MARK: - Initialization
init(dataManager: DataManagerObservable = .shared) {
self.dataManager = dataManager
}
// MARK: - Callbacks
var onSignInSuccess: ((Bool) -> Void)? // Bool indicates if user is verified

View File

@@ -33,6 +33,14 @@ final class OnboardingTasksViewModel: ObservableObject {
@Published private(set) var isSubmitting = false
@Published private(set) var submitError: String?
// MARK: - Private Properties
private let dataManager: DataManagerObservable
// MARK: - Initialization
init(dataManager: DataManagerObservable = .shared) {
self.dataManager = dataManager
}
// MARK: - Loads
func loadSuggestions(residenceId: Int32) async {

View File

@@ -31,8 +31,15 @@ class PasswordResetViewModel: ObservableObject {
// Cancellable delayed transition task
private var delayedTransitionTask: Task<Void, Never>?
// MARK: - Private Properties
private let dataManager: DataManagerObservable
// MARK: - Initialization
init(resetToken: String? = nil) {
init(
resetToken: String? = nil,
dataManager: DataManagerObservable = .shared
) {
self.dataManager = dataManager
// If we have a reset token from deep link, skip to password reset step
if let token = resetToken {
self.resetToken = token

View File

@@ -15,8 +15,13 @@ class RegisterViewModel: ObservableObject {
@Published var errorMessage: String?
@Published var isRegistered: Bool = false
// MARK: - Private Properties
private let dataManager: DataManagerObservable
// MARK: - Initialization
init() {}
init(dataManager: DataManagerObservable = .shared) {
self.dataManager = dataManager
}
// MARK: - Public Methods
func register() {

View File

@@ -14,10 +14,15 @@ class VerifyEmailViewModel: ObservableObject {
// MARK: - Private Properties
private let tokenStorage: TokenStorageProtocol
private let dataManager: DataManagerObservable
// MARK: - Initialization
init(tokenStorage: TokenStorageProtocol? = nil) {
init(
tokenStorage: TokenStorageProtocol? = nil,
dataManager: DataManagerObservable = .shared
) {
self.tokenStorage = tokenStorage ?? Dependencies.current.makeTokenStorage()
self.dataManager = dataManager
}
// MARK: - Public Methods

View File

@@ -66,6 +66,10 @@ struct iOSApp: App {
var body: some Scene {
WindowGroup {
RootView(deepLinkResetToken: $deepLinkResetToken)
// Single source of truth injection every descendant view can
// reach @EnvironmentObject var dataManager: DataManagerObservable
// without reading the .shared singleton implicitly.
.environmentObject(DataManagerObservable.shared)
.environmentObject(themeManager)
.environmentObject(contractorSharingManager)
.environmentObject(residenceSharingManager)