Building a SwiftUI App: Learning API Requests and Error Handling with PokeAPI

In the realm of app development, mastering the integration of APIs is crucial. The ability to fetch and handle data from external sources is a fundamental skill. Let’s dive into a simple yet insightful Swift app that interacts with the PokeAPI, fetching Pokémon data and learning key concepts along the way.


Model – Pokémon

The Pokemon struct is a fundamental part of our app’s data structure. It conforms to the Codable protocol, allowing for easy encoding and decoding of JSON data.

import Foundation

struct Pokemon: Codable {
    let id: Int
    let name: String
}

This struct defines the basic blueprint for a Pokémon object, containing two essential properties: id for the Pokémon’s identification number and name for its name.

PokeAPIManager

The PokeAPIManager class encapsulates the logic to interact with the PokeAPI and fetch Pokémon data based on the provided ID. It employs error handling using an enum called APIError to manage potential issues during the API request.

import Foundation
import SwiftUI

enum APIError: Error {
    case invalidURL
    case requestFailed
}

final class PokeAPIManager {
    func fetchPokemon(withID id: Int, completion: @escaping(Result<Pokemon, APIError>) -> Void) {
        guard let url = URL(string: "https://pokeapi.co/api/v2/pokemon/\(id)") else {
            completion(.failure(.invalidURL))
            return
        }

        let task = URLSession.shared.dataTask(with: url) { data, _, error in
            if let error = error {
                completion(.failure(.requestFailed))
                print("Error: \(error.localizedDescription)")
                return
            }

            guard let data = data else {
                completion(.failure(.requestFailed))
                return
            }

            do {
                let pokemon = try JSONDecoder().decode(Pokemon.self, from: data)
                completion(.success(pokemon))
            } catch {
                completion(.failure(.requestFailed))
                print("Error decoding data: \(error.localizedDescription)")
            }
        }
        task.resume()
    }
}

The fetchPokemon method takes an id parameter and a completion handler as its arguments. Inside this method, it constructs a URL based on the given ID, creates a data task using URLSession, and then manages potential errors and decodes the retrieved data. Upon successful retrieval, it calls the completion handler with a .success case containing the fetched Pokémon or a .failure case with the corresponding error from the APIError enum.

PokemonView – SwiftUI Interface

The PokemonView struct is a SwiftUI view that serves as the user interface for our app. It displays the Pokémon information fetched from the API and includes functionality to trigger the data retrieval process.

import SwiftUI

struct HomeView: View {
    @State private var pokemon: Pokemon?
    private let apiManager = PokeAPIManager()

    var body: some View {
        VStack(spacing: 28) {
            if let pokemon = pokemon {
                Text("ID: \(pokemon.id)")
                Text("Pokemon Name: \(pokemon.name)")
            } else {
                ProgressView()
            }
            Button("FetchPokemon") {
                fetchPokemon()
            }
        }
        .onAppear {
            fetchPokemon()
        }
    }

    private func fetchPokemon() {
        let randomID = Int.random(in: 1...100)
        apiManager.fetchPokemon(withID: randomID) { result in
            DispatchQueue.main.async {
                switch result {
                case .success(let fetchedPokemon):
                    pokemon = fetchedPokemon
                case .failure(let error):
                    print("Error fetching Pokemon: \(error)")
                }
            }
        }
    }
}

This view contains a @State property pokemon to store the fetched Pokémon data. It includes a fetchPokemon method triggered by a button press, initiating the API request to retrieve Pokémon data. Upon receiving the result from the apiManager, it updates the UI with the fetched Pokémon or handles any encountered errors.

Conclusion

In the realm of Swift app development, integrating APIs and handling asynchronous data retrieval are essential skills. Through the exploration of a Pokémon-fetching sample app utilizing the PokeAPI, several fundamental concepts have been highlighted.