// In my root view aka Parent View
import SwiftUI
struct RootView: View {
@StateObject private var devices = DevicesViewModel()
var body: some View {
ScrollView {
UnitStatusCard(devicesVM: devices)
}
.onAppear() {
devices.fetchDevices()
}
}
}
// DeviceViewModel.swift - Parent View Model
import Foundation
class DevicesViewModel: ObservableObject {
@Published var models: [Device] = []
private let networkManager = NetworkManager<[Device]>()
init() {
fetchDevices()
}
public func fetchDevices() {
Task {
do {
if let unitId = UserDefaults.standard.string(forKey: kUnit) {
let models = try await networkManager.fetchData(path: "/api/test")
DispatchQueue.main.async {
self.models = models
}
}
} catch {...}
}
}
}
// UnitStatusCard.swift - Child View
struct UnitStatusCard: View {
@StateObject var unitStatusCardVM: UnitStatusCardViewModel
init(devicesVM: DevicesViewModel) {
self._unitStatusCardVM = StateObject(wrappedValue: UnitStatusCardViewModel(devicesVM: devicesVM))
}
var body: some View {
StatusView()
.onAppear() {
unitStatusCardVM.getStatusMeta()
}
}
}
// UnitStatusCardViewModel.swift - Child View Model
class UnitStatusCardViewModel: ObservableObject {
@Published var value: String = "Good"
var devicesVM: DevicesViewModel
init(devicesVM: DevicesViewModel) {
self.devicesVM = devicesVM
}
public func getStatusMeta() {
print(devicesVM.models) // value is [], WHY??
}
}
In DeviceViewModel.swift
, there is a Api call, the result is fetched succesfully without error.
However, when I pass the result to my child view model (UnitStatusCardViewModel
), the value is empty even it’s correctly fetched according to ProxyMan.
public func getStatusMeta() {
print(devicesVM.models) // value is [], WHY??
}
Why is that and how to fix it?
2
Answers
I think there are several things that may lead to this result. The
fetchDevices
method is performing an asynchronous operations inside (although is not marked async, it executes a Task and seems to perform a sort of network call. Depending on when you check the model the value gets printed before the fetch (eg. thegetStatusMeta
is called ononAppear
, likely before the fetch to complete his inner execution.I would recommend not running the
fetchDevices
in the DeviceViewModel init method, it’s anyway called in the onAppear of the RootView, and this means that it get called twice, and plus, eventual actions leading to an ui change at the creation of an object, before or while it gets attached to a Swift UI view, may lead to undesirable result in terms of ui threading (those infamous violet/purple warnings that may show up in Xcode during development)one more thing: In vision of being stricted concurrency check compliant, it is strongly reccomended to not use the old fashioned DispatchAsync as you did
rather change it in
or even mark
the view model as @MainActor may be an alternative, so any change on the DeviceViewModel properties will be isolated and executed intrinsically on main thread
In SwiftUI the
View
structs are the view model and to use async/await it is.task
, e.g.NetworkManager
should be a struct that holds the async funcs and its in theEnvironment
so it can be mocked for Previews.