I'm trying to access "Genre Categories" data from Deezer's API (url: https://api.deezer.com/genre) in my SwiftUI Project, however in the line where I decode and assign the data value to my array, it doesn't happen. Instead the program falls to my else condition. I've used this exact program structure on other API's ( Apple's iTunes API and other public test APIs) and it works with no problem whatsoever. Could there be a problem in Deezer API or my request itself?
My GenreModel Code: '''
import Foundation
struct Genre: Codable {
let id: Int
let name: String
let picture: String
}
'''
My GenreViewModel Code:
'''
import Foundation
final class GenreViewModel: ObservableObject{
@Published var genres: [Genre] = []
@Published var hasError = false
@Published var error: UserError?
func fetchGenres(){
hasError = false
let genreUrl = "https://api.deezer.com/genre"
if let url = URL(string: genreUrl){
URLSession.shared.dataTask(with: url){ [weak self] data,response, error in
DispatchQueue.main.async {
if let error = error{
self?.hasError = true
self?.error = UserError.custom(error: error)
}
else{
let decoder = JSONDecoder()
//decoder.keyDecodingStrategy = .useDefaultKeys
if let data = data,
let genres = try? decoder.decode([Genre].self, from: data){
self?.genres = genres
}
else{
self?.hasError = true
self?.error = UserError.failedToDecode
}
}
}
}.resume()
}
}
}
extension GenreViewModel{
enum UserError: LocalizedError{
case custom(error: Error)
case failedToDecode
var errorDescription: String?{
switch self{
case .failedToDecode:
return "Failed to decode"
case .custom(let error):
return error.localizedDescription
}
}
}
}
'''
My GenreView Code '''
import SwiftUI
struct GenreView: View{
let genre: Genre
var body: some View{
VStack(alignment: .leading){
Text("**Genre**: \(genre.id)")
Text("**Name**: \(genre.name)")
Text("**Picture**: \(genre.picture)")
}
}
}
struct GenreView_Previews: PreviewProvider{
static var previews: some View{
GenreView(genre: .init(id: 0, name: "a", picture: "pic"))
}
}
'''
My ContentView Code
'''
import SwiftUI
struct ContentView: View {
@StateObject private var gVM = GenreViewModel()
var body: some View {
NavigationView{
ZStack{
List{
ForEach(gVM.genres, id: \.id){ genre in
GenreView(genre: genre).listStyle(.plain)
}
}.listStyle(.plain).navigationTitle("Users")
.alert(isPresented: $gVM.hasError, error: gVM.error){
Button(action: gVM.fetchGenres){
Text("try again")
}
}
}.onAppear(perform: gVM.fetchGenres)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
'''
Try this example code, where the root ApiResponse
is used to decode the json data you get from the server.
struct ApiResponse: Codable {
let data: [Genre]
}
struct Genre: Identifiable, Codable {
let id: Int
let name, picture, type: String
let pictureSmall, pictureMedium, pictureBig, pictureXl: String
enum CodingKeys: String, CodingKey {
case id, name, picture, type
case pictureSmall = "picture_small"
case pictureMedium = "picture_medium"
case pictureBig = "picture_big"
case pictureXl = "picture_xl"
}
}
final class GenreViewModel: ObservableObject {
@Published var genres: [Genre] = []
@Published var hasError = false
@Published var error: UserError?
func fetchGenres() {
hasError = false
let genreUrl = "https://api.deezer.com/genre"
if let url = URL(string: genreUrl) {
URLSession.shared.dataTask(with: url){ data,response, error in
DispatchQueue.main.async {
if let error = error {
self.hasError = true
self.error = UserError.custom(error: error)
} else {
if let data = data {
do {
let decoder = JSONDecoder()
let genres = try decoder.decode(ApiResponse.self, from: data)
self.genres = genres.data
return
} catch {
print(error)
}
}
self.hasError = true
self.error = UserError.failedToDecode
}
}
}.resume()
}
}
}