I want to refactor the API call that is made using async and await but I am getting the error as the publishing needs to be done on the main thread.
The below is the code that I wrote in the file named LogIn View: -
@State private var quotes = [Quote]()
var body: some View {
NavigationView {
List(quotes, id:\.quote_id) { quote in
VStack(alignment: .leading) {
Text(quote.author)
.font(.headline)
Text(quote.quote)
.font(.body)
}
}
.padding()
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Log out") {
authentication.updateValidation(success: false)
}
}
}
.navigationTitle("Dashboard Screen ")
}
.task {
await fetchData()
}
}
func fetchData() async {
//create url
guard let url = URL(string: "https://breakingbadapi.com/api/quotes") else {
print("URL does not work")
return
}
//fetch data from url
do {
let (data, _) = try await URLSession.shared.data(from: url)
//decode that data
if let decodeResponse = try? JSONDecoder().decode([Quote].self, from: data) {
quotes = decodeResponse
}
} catch {
print("Data not valid")
}
}
I want to write the function fetchData() in a separate file and use it here in LogIn View but upon trying to do so I am getting the error mentioned above. Can anyone Please help me with this.
PS:- all the variables are defined inside another file named variables . The code for that is as follows:-
import Foundation
struct Quote: Codable {
var quote_id: Int
var quote: String
var author: String
var series: String
}
A nice place is in an extension of NSURLSession
, eg
extension NSURLSession {
func fetchQuotes() async throws -> [Quote] {
//create url
guard let url = URL(string: "https://breakingbadapi.com/api/quotes") else {
print("URL does not work")
return
}
//fetch data from url
let (data, _) = try await data(from: url)
//decode that data
return try JSONDecoder().decode([Quote].self, from: data)
}
}
Then you can simply do:
.task {
do {
quotes = try await URLSession.shared.fetchQuotes()
} catch {
errorMessage = error.description
}
}
This has the advantage you can use it with a different kind of URLSession
, eg for API requests we usually use an ephemeral session. Another good place would be a static async func in the Quote
struct.
to put your func fetchData()
in a model and avoid the error, try this approach:
class QuotesModel: ObservableObject {
@Published var quotes = [Quote]()
@MainActor // <-- here
func fetchData() async {
guard let url = URL(string: "https://breakingbadapi.com/api/quotes") else {
print("Invalid URL")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
quotes = try JSONDecoder().decode([Quote].self, from: data)
} catch {
print(error)
}
}
}
struct ContentView: View {
@StateObject var model = QuotesModel()
var body: some View {
NavigationView {
List(model.quotes, id: \.quote_id) { quote in
VStack(alignment: .leading) {
Text(quote.author)
.font(.headline)
Text(quote.quote)
.font(.body)
}
}
.padding()
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Log out") {
// authentication.updateValidation(success: false)
}
}
}
.navigationTitle("Dashboard Screen ")
}
.task {
await model.fetchData()
}
}
}
struct Quote: Codable {
var quote_id: Int
var quote: String
var author: String
var series: String
}
depending on your code it may just be as simple as putting the code giving you the error inside await MainActor.run {... }
That said, as a general rule async code is easier to manage when it returns values to use, rather than setting variables from inside functions.
class SomeViewModel:ObservableObject {
func fetchData() async -> [Quotes] {
...
if let decodeResponse = try? JSONDecoder().decode([Quote].self, from: data) {
return decodeResponse
}
...
}
}
struct TheView: View {
@StateObject var viewModel = SomeViewModel()
@State private var quotes = [Quote]()
var body: some View {
NavigationView {
}
.task {
quotes = await viewModel.fetchData()
}
}
}
Make @MainActor method with @Published in ObservableObject class. and for model use codable.
struct QuoteView: View {
@State var quotes: [Quote] = []
@ObservedObject var quoteStore = QuoteStore()
var body: some View {
NavigationView {
List(quotes, id:\.quote_id) { quote in
VStack(alignment: .leading) {
Text(quote.author)
.font(.headline)
Text(quote.quote)
.font(.body)
}
}
.navigationTitle("Quotes")
}
.task {
quotes = try! await quoteStore.fetchData()
}
}
}
struct Quote: Codable {
let quote_id = UUID()
let quote: String
let author: String
}
class QuoteStore: ObservableObject {
@Published var quotes: [Quote] = []
@MainActor
func fetchData() async throws -> [Quote] {
guard var url = URL(string: "https://breakingbadapi.com/api/quotes") else { throw AppError.invalidURL }
let (data, _) = try await URLSession.shared.data(from: url)
let repos = try JSONDecoder().decode([Quote].self, from: data)
return repos
}
}
enum AppError: Error {
case invalidURL
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.