I’m working on a project using mealDB, and this is my first time working with a json DB and none of the data is populating in my app. I have a function:
func fetchDesserts() async throws -> [Meal] {
let urlString = "https://themealdb.com/api/json/v1/1/filter.php?c=Dessert"
guard let url = URL(string: urlString) else {
throw URLError(.badURL)
}
let (data, _) = try await URLSession.shared.data(from: url)
let mealResponse = try JSONDecoder().decode(MealResponse.self, from: data)
var meals: [Meal] = []
for mealData in mealResponse.meals {
guard let imageUrl = URL(string: mealData.strMealThumb) else {
continue
}
let (imageData, _) = try await URLSession.shared.data(from: imageUrl)
if let image = UIImage(data: imageData) {
let meal = Meal(name: mealData.strMeal, image: image, id: Int(mealData.idMeal) ?? 0)
meals.append(meal)
}
}
return meals
}
That I call here:
struct ContentView: View {
@StateObject var model = MealViewModel()
var body: some View {
NavigationStack {
VStack {
// Dessert Button
NavigationLink(destination: {
DessertView(desserts: model.desserts)
.onAppear(perform: {
Task {
do {
model.desserts = try await fetchDesserts()
} catch {
print(error)
}
}
})
}, label: {
ZStack {
RoundedRectangle(cornerRadius: 25.0)
.frame(width: 200, height: 100)
Text("Desserts")
.foregroundStyle(.white)
}
})
}
.padding()
}
}
}
I don’t know if I improperly structured my query or am calling it incorrectly. On the mealDB website they state to use the API key "1" for development but I don’t know where I would specify the API key. If someone can provide some feedback it would be much appreciated.
EDIT
I have updated the implementation of the task and modeled the query after workingDogs query from the attached repo yet I still receive the error nw_connection_copy_connected_local_endpoint_block_invoke [C1] Connection has no local endpoint
.
Here is my updated Code:
struct DessertView: View {
// for procesing fetch request from json URL
@State private var processing: Bool = false
// desserts collectd
@State private var desserts = [Meal]()
var body: some View {
VStack {
if processing {
ProgressView(label: {
Text("Loading Desserts...")
})
}
else {
// Standard View
}
}
.task { // Query from mealsDB
processing = true
let response: ApiResponse? = await fetchMeals()
processing = false
}
}
// Fetch Meals Func
func fetchMeals<T: Decodable>() async -> T? {
// for testing
// desserts
let url = URL(string: "https://themealdb.com/api/json/v1/1/filter.php?c=Dessert")!
let request = URLRequest(url: url)
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
// throw URLError(.badServerResponse) // todo
print(URLError(.badServerResponse))
return nil
}
return try JSONDecoder().decode(T.self, from: data)
}
catch {
return nil
}
}
}
struct Meal: Decodable, Identifiable {
var id: String
var name: String?
var imgURL: String? // String that represents a url of an image of the meal
enum CodingKeys: String, CodingKey {
case id = "idMeal"
case name = "strMeal"
case imgURL = "strMealThumb"
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.name = try container.decodeIfPresent(String.self, forKey: .name)
self.imgURL = try container.decodeIfPresent(String.self, forKey: .imgURL)
}
}
struct ApiResponse: Decodable {
var meals: [Meal]?
}
2
Answers
In SwiftUI it’s
.task
, notTask
and it removes the need for the old@StateObject
, so you can simply do:Best to use
Environment
for your async funcs controller struct so it can be mocked in Previews.Try this example code based on your original code and my
fetchMeals
, works well for me.