Unable to Parse API for Exchange Rate

We are new programmers and are trying to parse an Exchange Rate API. We are able to pull data from the API, but are unable to extract and use that data.


Line 54 shows the API is working, and will show the correct data in the debug area.


Line 58 is, we believe, the problem area.


Any suggestions are appreciated.


Kind Regards,


import UIKit

struct ExchangeRates: Decodable {
    var base: String = ""
    var rates: [String: Double]
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
       
        getExRate(currencyBase: "USD")
        
        //this whole for-in loop did not work
        for (key, value) in updatedExRates {
            // var exchangeRateValue: Double
               if (key == "HUF"){
                  // exchangeRateValue = value
                    print(value)
                   displayLabel.text! = "\(key): \(value)"
               }
           }
    }
    
    @IBOutlet weak var displayLabel: UILabel!
    
    var updatedExRates = [String: Double]()
    
    func getExRate(currencyBase: String) {
    //        ((completion: @escaping(Result<exchangerates, currencyerror="">) -> Void))
        let resourceString = "https://api.exchangeratesapi.io/latest?base=\(currencyBase)"
                   
        let resourceURL = URL(string: resourceString)
       
        print(resourceURL!)
        
        guard resourceURL != nil else{
            return
        }
        
        let session = URLSession.shared
        
        
        let dataTask = session.dataTask(with: resourceURL!) { (data, response, error) in
                
                //check for error
            if error == nil && data != nil {
                    //parse JSON
                    let decoder = JSONDecoder()
                        do{
                            let exRates = try decoder.decode(ExchangeRates.self, from: data!)
                            print(exRates) //this works(see debug area)
                            
                            // this is where the issue started to occur.
                            //It does not assign the dictionary to this variable I have created(returns nil instead).
                            self.updatedExRates = exRates.rates
                    }
                    catch {
                        print("error in JSON parsing")
                    }
                }
            }
            //Make the API call
            dataTask.resume()
        }
}

Accepted Reply

Please post what you get on logs from print.


And add this print, to tell what you get as well:


        print("Dictionary", updatedExRates)
        for (key, value) in updatedExRates {
            // var exchangeRateValue: Double
               if (key == "USD"){
                  let exchangeRateValue = value
                    print(value)
                   displayLabel.text! = String(exchangeRateValue)
               }
           }


Probably, at this stage, dict is empty.

The problem is probably an async problem.

Here is what happens in the following code (I reformat a little to get declarations at top (just for easier reading):


class ViewController: UIViewController {

    @IBOutlet weak var displayLabel: UILabel!
    
    var updatedExRates = [String: Double]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
       
        getExRate(currencyBase: "USD")
        
        //this whole for-in loop did not work
        for (key, value) in updatedExRates {
            // var exchangeRateValue: Double
               if (key == "HUF"){
                  // exchangeRateValue = value
                    print(value)
                   displayLabel.text! = "\(key): \(value)"
               }
           }
    }
    
    func getExRate(currencyBase: String) {


  • Line 11, you get the exchange rate and load updatedExRates.
  • This loading (in getExRate) is executed in another thread for session.dataTask
  • So, line 11 calls the func ; a new thread is opened for session.dataTask
  • But, before completion, viewDidload continues to line 14 ;
  • Thus, updatedExRates is not yet loaded, it is still nil.


A way to solve it is to call the label update after task has completed.


Create a func


func updateLabel() {
       for (key, value) in updatedExRates { 
            // var exchangeRateValue: Double 
               if (key == "HUF"){ 
                  // exchangeRateValue = value 
                    print(value) 
                   DispatchQueue.main.async {     // In case we're called from another thread
                       displayLabel.text! = "\(key): \(value)" 
                   }
               } 
           } 
}


Remove this part from viewDidLoad and add it in getExRate:


        let dataTask = session.dataTask(with: resourceURL!) { (data, response, error) in
                
                //check for error
            if error == nil && data != nil {
                    //parse JSON
                    let decoder = JSONDecoder()
                        do{
                            let exRates = try decoder.decode(ExchangeRates.self, from: data!)
                            
                            // this is where the issue started to occur.
                            //It does not assign the dictionary to this variable I have created(returns nil instead).
                            self.updatedExRates = exRates.rates
                            updateLabel()          // Update will be done in main thread
                    }
                    catch {
                        print("error in JSON parsing")
                    }
                }
            }
            dataTask.resume()
        }

Replies

What do you get from the print line 54 ?


Complement log with:

print(exRates, "  -> rates", exRates.rates)


Where do you test that self.updatedExRates is nil ?

Good questions Claude,

Line 54 displayed all of the exchange rates....so that worked.


I added your code and see that, indeed, it is not nil and that the exchange rates are passing to updatedExRates. Thanks for that.


Could you help us extract the USD exchange rate and display it in the label? We tried to do this inside viewDidLoad() with the for loop below, but that did not work. Line 6 below has a print command, but that only provided the url for the API rather than extracting the USD rate.


Thanks for your help,

Mike


//this whole for-in loop did not work
        for (key, value) in updatedExRates {
            // var exchangeRateValue: Double
               if (key == "USD"){
                  let exchangeRateValue = value
                    print(value)
                   displayLabel.text! = String(exchangeRateValue)
               }
           }

Please post what you get on logs from print.


And add this print, to tell what you get as well:


        print("Dictionary", updatedExRates)
        for (key, value) in updatedExRates {
            // var exchangeRateValue: Double
               if (key == "USD"){
                  let exchangeRateValue = value
                    print(value)
                   displayLabel.text! = String(exchangeRateValue)
               }
           }


Probably, at this stage, dict is empty.

The problem is probably an async problem.

Here is what happens in the following code (I reformat a little to get declarations at top (just for easier reading):


class ViewController: UIViewController {

    @IBOutlet weak var displayLabel: UILabel!
    
    var updatedExRates = [String: Double]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
       
        getExRate(currencyBase: "USD")
        
        //this whole for-in loop did not work
        for (key, value) in updatedExRates {
            // var exchangeRateValue: Double
               if (key == "HUF"){
                  // exchangeRateValue = value
                    print(value)
                   displayLabel.text! = "\(key): \(value)"
               }
           }
    }
    
    func getExRate(currencyBase: String) {


  • Line 11, you get the exchange rate and load updatedExRates.
  • This loading (in getExRate) is executed in another thread for session.dataTask
  • So, line 11 calls the func ; a new thread is opened for session.dataTask
  • But, before completion, viewDidload continues to line 14 ;
  • Thus, updatedExRates is not yet loaded, it is still nil.


A way to solve it is to call the label update after task has completed.


Create a func


func updateLabel() {
       for (key, value) in updatedExRates { 
            // var exchangeRateValue: Double 
               if (key == "HUF"){ 
                  // exchangeRateValue = value 
                    print(value) 
                   DispatchQueue.main.async {     // In case we're called from another thread
                       displayLabel.text! = "\(key): \(value)" 
                   }
               } 
           } 
}


Remove this part from viewDidLoad and add it in getExRate:


        let dataTask = session.dataTask(with: resourceURL!) { (data, response, error) in
                
                //check for error
            if error == nil && data != nil {
                    //parse JSON
                    let decoder = JSONDecoder()
                        do{
                            let exRates = try decoder.decode(ExchangeRates.self, from: data!)
                            
                            // this is where the issue started to occur.
                            //It does not assign the dictionary to this variable I have created(returns nil instead).
                            self.updatedExRates = exRates.rates
                            updateLabel()          // Update will be done in main thread
                    }
                    catch {
                        print("error in JSON parsing")
                    }
                }
            }
            dataTask.resume()
        }

Bingo! You nailed it and taught us a few good lessons along the way.

Thanks so much for taking time to help a group of novice.

Works perfectly.

Respectfully,

Mike 🙂