Reputation: 57
So Im writing a simple lyrics app that uses an API that receives a song name and artist and returns the lyrics for it. Everything works as expected if I send proper song names and artists, but im having a hard time detecting errors from the view and reacting accordingly such as showing an alert before the sheet gets rendered or something. I mean an error such as the user entering a song or artist name with a typo, which would make the API to not get lyrics for such song. When the lyrics are not found, the API returns a 400 not found HTTP code, maybe I can check for such an error code somewhere on the api method call and check later from the view or similar?
This is my view, simply makes the api call if theres an internet connection available, which toggles the sheet to render:
HStack {
Spacer()
Button(action: {
if(!NetworkMonitor.shared.isConnected) {
self.noConnectionAlert.toggle()
} else {
viewModel.loadApiSongData(songName: songName, artistName: artistName)
//self.showingLyricsSheet = true
}
}, label: {
CustomButton(sfSymbolName: "music.note", text: "Search Lyrics!")
})
.alert(isPresented: $noConnectionAlert) {
Alert(title: Text("No internet connection"), message: Text("Oops! It seems you arent connected to the internet. Please connect and try again!"), dismissButton: .default(Text("Got it!")))
}
Spacer()
}
.padding(.top, 20)
.sheet(item: $viewModel.apiResponse) { item in
LyricsView(vm: self.viewModel, songName: songName, artistName: artistName, arrLyrics: item.apiValues)
}
This is my API calling method:
class ViewModel : ObservableObject {
@Published var searchedSongs = [SongDetails]() {
didSet {
print(searchedSongs)
}
}
@Published var apiResponse : APIResponse?
func loadApiSongData(songName: String, artistName: String) {
let rawUrl = "https://api.lyrics.ovh/v1/\(artistName)/\(songName)"
let fixedUrl = rawUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
print("Old url: \(rawUrl)")
print("New url: \(fixedUrl!)")
guard let url = URL(string: fixedUrl!) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(Song.self, from: data) {
// we have good data – go back to the main thread
DispatchQueue.main.async {
// update our UI
print("Found good lyrics")
if(!self.songAlreadySearched(songName: songName)) {
let song = SongDetails(songName: songName, artistName: artistName, lyrics: decodedResponse.lyrics)
self.searchedSongs.append(song)
}
self.apiResponse = APIResponse(apiValues: [decodedResponse.lyrics])
}
// everything is good, so we can exit
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
Upvotes: 0
Views: 625
Reputation: 329
@Published var apiError: APIError? // Added error enum in ViewModel. Use this in your view. Add message string in enum whatever you want to display in UI
URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.responseHandler(data, response, error, songName, artistName)
}
}.resume()
And the handler function
private func responseHandler(_ data: Data?,
_ response: URLResponse?,
_ error: Error?,
_ songName: String,
_ artistName: String) {
if let error = error {
if error._code == -1009 {
apiError = .offline
} else {
apiError = .sessionError
}
return
}
guard let response = response as? HTTPURLResponse, let data = data else {
apiError = .missingDataError
return
}
guard (200..<300).contains(response.statusCode) else {
switch Status(rawValue: response.statusCode) {
case .requestTimeout:
apiError = .timeoutError
case .internalServerError:
apiError = .internalServerError
case .notFound:
apiError = .notFound
default:
apiError = .requestError
}
return
}
do {
let decodedResponse = try JSONDecoder().decode(Song.self, from: data)
if(!songAlreadySearched(songName: songName)) {
let song = SongDetails(songName: songName, artistName: artistName, lyrics: decodedResponse.lyrics)
searchedSongs.append(song)
}
apiResponse = APIResponse(apiValues: decodedResponse.lyrics)
} catch {
apiError = .parsingError
}
}
Error enums
enum APIError: Error {
case sessionError
case parsingError
case missingDataError
case requestError
case timeoutError
case offline
case internalServerError
case notFound
case genericError
}
enum Status: Int {
case multipleChoices = 300
case badRequest = 400
case forbidden = 403
case notFound = 404
case requestTimeout = 408
case internalServerError = 500
case notImplemented = 501
case badGateway = 502
case serviceUnavailabe = 503
case gatewayTimeout = 504
}
Upvotes: 2