Home > Software engineering >  Create object using multiple independent API calls
Create object using multiple independent API calls

Time:08-06

I am working on a practice project with the CoinGecko API. I just figured out how to successfully fetch, parse, and display data from the API, but I have ran into an issue. Currently I am only fetching a single coin, using a single URL but I would like to be able to fetch multiple (3 in this case), all at once and display them. Not sure how to proceed, or if my explanation makes sense so I have attached my code below. Thanks in advance.

CoinListViewModel

import Foundation

class CoinListViewModel {
    private(set) var coin: Coin
    
    init(coin: Coin) {
        self.coin = coin
    }
    
    func getCoins(url: URL) async {
        do {
            let coin = try await WebService().getCoins(url: url)
            
            self.coin = coin
            
        } catch {
            print(error)
        }
    }
}

struct CoinViewModel {
    private let coin: Coin
    
    init(coin: Coin) {
        self.coin = coin
    }
    
    var symbol: String {
        coin.symbol
    }
    
    var name: String {
        coin.name 
    }
    
    var price: [String: Double] {
        coin.marketData.currentPrice
    }
}

WebService

import Foundation

enum CoinsError: Error {
    case invalidServerResponse
}

class WebService {
    
    func getCoins(url: URL) async throws -> Coin {
        
        let (data, response) = try await URLSession.shared.data(from: url)
        
        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
                throw CoinsError.invalidServerResponse
            }
        
        return try JSONDecoder().decode(Coin.self, from: data)
    }
}

Constants

import Foundation

struct Constants {
    struct coinURLs {
        static let allCoinURLs = [
            URL(string: "https://api.coingecko.com/api/v3/coins/bitcoin")!,
            URL(string: "https://api.coingecko.com/api/v3/coins/ethereum")!,
            URL(string: "https://api.coingecko.com/api/v3/coins/litecoin")!
        ]
    }
}

Coin

import Foundation

// MARK: - WelcomeElement
struct Coin: Codable {
    let symbol, name: String
    let marketData: MarketData

    enum CodingKeys: String, CodingKey {
        case symbol, name
        case marketData = "market_data"
    }
    
    static var DefaultCoin = Coin(symbol: " ", name: " ", marketData: MarketData(currentPrice: ["usd": 0.0]))
}

struct Tion: Codable {
    let en: String
    
    enum CodingKeys: String, CodingKey {
        case en
    }
}

struct MarketData: Codable {
    let currentPrice: [String: Double]
    
    enum CodingKeys: String, CodingKey {
        case currentPrice = "current_price"
    }
}

CoinListViewController

import Foundation
import UIKit

class CoinListViewController: UITableViewController {
    
    private let vm = CoinListViewModel(coin: Coin.DefaultCoin)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
        
        Task {
            await getCoins()
        }
    }
    
    private func configureUI() {
        self.navigationController?.navigationBar.prefersLargeTitles = true
        self.title = "CoinFlip"
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "StockCell")
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        1
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "StockCell", for: indexPath)
        let coin = vm.coin
        
        var content = cell.defaultContentConfiguration()
        content.text = coin.name
        content.secondaryText = "$\(Int(coin.marketData.currentPrice["usd"] ?? 0))"
        cell.contentConfiguration = content
        
        return cell
    }
    
    
    private func getCoins() async {
        await vm.getCoins(url: Constants.coinURLs.allCoinURLs[0])
        print(vm.coin)
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }
}

CodePudding user response:

You can spawn multiple network calls if you make a few changes to your WebService. This will also return a partial result if fetching a coin fails.

func getCoins(urls: [URL]) async throws -> [Coin] {
    return try await withThrowingTaskGroup(of: Coin.self, body: { taskGroup in
        var coins = [Coin]()
        
        urls.forEach { url in
            taskGroup.addTask {
                let (data, response) = try await URLSession.shared.data(from: url)
                
                guard
                    let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200
                else { return Coin() }
                
                return try JSONDecoder().decode(Coin.self, from: data)
            }
        }
        
        for try await coin in taskGroup {
            coins.append(coin)
        }
        
        return coins
    })
}
  • Related