I'm slowly going crazy because my view doesn't always update, and I can't figure out what's causing the problem. I've added @MainActor for network calls. The picker on CreateInvoiceDetails view is empty and doesn’t show currencies on the first view appearance. This happens ~90% of the time. But if I go back and return to the screen, it shows correctly. Its not happening only with arrays its happening with pure strings.
I am creating viewmodel in HomeScreen and passing it to next view through init.
struct HomeScreen: View {
var createInvoiceViewmodel = CreateInvoiceViewModel()
var body: some View {
VStack {
Button {
createInvoiceViewmodel.navPub.send(.createInvoiceGoTo(.details))
} label: {
Text("New Invoice")
.style(.labelMedium)
}
}
.navigationDestination(for: CreateInvoiceFlow.self) { screen in
switch screen {
case .details:
CreateInvoiceDetails(viewModel: createInvoiceViewmodel)
}
}
}
In CreateInvoiceDetails I tried making network call in onAppear of my view or in a task. None of the approaches work consistently.
struct CreateInvoiceDetails2: View {
@Bindable var viewModel: CreateInvoiceViewModel
var body: some View {
VStack {
Text(viewModel.selectedCurrency?.name ?? "-")
Picker(selection: $viewModel.selectedCurrency) {
ForEach(viewModel.currencies) { c in
Text(c.name)
.tag(c)
}
} label:{
Text("")
}
}
.task {
await viewModel.getConstants()
}
// .onAppear {
// Task {
// await viewModel.getConstants()
// }
// }
}
}
The network call succeeds, always returns the same result, and the print statement works always.
@Observable
class CreateInvoiceViewModel: BaseViewModel {
var selectedCurrency: Currency?
var currencies: [Currency] = []
@MainActor
func getConstants() async {
loadingState = .getConstants
let response = await repository.getConstants()
switch response {
case .result(let everyResponse):
guard let c = everyResponse.data?.currencies
.map({ $0.value})
else { return }
currencies.removeAll()
currencies.append(contentsOf: c)
// currencies = c. // also not working
print("DEBUGPRINT: currencies: ", c)
selectedCurrency = c.first
}
loadingState = .no
}
}
@Observable
class BaseViewModel {
var loadingState: LoadingState = .no
let repository = AppRepository.shared
var navPub = PassthroughSubject<NavigationHelper, Never>()
}
Currency model is:
struct Currency: Decodable, Hashable, Identifiable, Equatable {
let symbol: String
let name: String
let code: String
var id: String {
code
}
}
But my view is not updating every time. Mostly the first time when I run it. Is @Observable reliable, or should I go back and use ObservedObject and @Published ?
An observable type should be used either as a State or Environment object in a view so change the declaration in HomeScreen
to
@State private var createInvoiceViewmodel = CreateInvoiceViewModel()
You must use State
& Bindable
since the sub-view updates the observable object, if the sub-view is only displaying the data of the object it can be let
declared.