1

the following is the code I am trying to get to work, all I want to do is display the current price and the currency it is in.

the link returns a format of : {"currency":"USD","rate":"178.0466666666667"}

import SwiftUI
import Combine

struct BitcoinPrice: Decodable, Identifiable {
    var id = UUID()
    let currency: String
    let rate: String
}


struct BSVPrice: View {
    @State private var requests = Set<AnyCancellable>()
    @State private var exchangeRate = [BitcoinPrice]()
    
    var body: some View {
        NavigationView {
            List(exchangeRate) { rate in
                VStack(alignment: .leading) {
                    Text(rate.currency)
                    Text(rate.rate)
                }
            }
            .navigationTitle("Bitcoin Price")
        }
        
        .onAppear {
            let bitcoinPriceURL = URL(string: "https://api.whatsonchain.com/v1/bsv/main/exchangerate")!
            //let bitcoinPriceTask = fetch(bitcoinPriceURL, defaultValue: [BitcoinPrice]() )
            fetch(bitcoinPriceURL, defaultValue: [BitcoinPrice]() ) {
                exchangeRate = $0
            }
        }
    }
    
    func fetch<T: Decodable>(_ url: URL, defaultValue: T, completion: @escaping (T) -> Void) {
        let decoder = JSONDecoder()
        
        URLSession.shared.dataTaskPublisher(for: url)
            .retry(1)
            .map(\.data)
            .decode(type: T.self, decoder: decoder)
            .replaceError(with: defaultValue)
            .receive(on: DispatchQueue.main)
            .sink(receiveValue: completion)
            .store(in: &requests)
    }
    
    func fetch<T: Decodable>(_ url: URL, defaultValue: T) -> AnyPublisher<T, Never> {
        let decoder = JSONDecoder()
        
        return URLSession.shared.dataTaskPublisher(for: url)
            .retry(1)
            .map(\.data)
            .decode(type: T.self, decoder: decoder)
            .replaceError(with: defaultValue)
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
}


struct BSVPrice_Previews: PreviewProvider {
    static var previews: some View {
        BSVPrice()
    }
}

Does anyone know why the JSON is not loading into the application? Any help would be great, I have got the code to work with a different API so a bit confused.

1
  • 2
    You should get and print the Decoding error somehow. There is an error. Commented Jul 21, 2020 at 16:27

1 Answer 1

1

First you're suppressing your error by using .replaceError(with: defaultValue) which will hide any errors and replace them with a default value (here an empty list).


In

func fetch<T: Decodable>(_ url: URL, defaultValue: T, completion: @escaping (T) -> Void)

you're trying to decode T.self:

.decode(type: T.self, decoder: decoder)

which should be of BitcoinPrice type to work.

Note that the response from your link is a single object, not an array.

However, in your view, you pass [BitcoinPrice] as the default value:

fetch(bitcoinPriceURL, defaultValue: [BitcoinPrice]() ) { ...

which infers T to be of type [BitcoinPrice].

Also remove var id = UUID() from BitcoinPrice:

struct BitcoinPrice: Decodable {
    let currency: String
    let rate: String
}

and use it like this:

List(exchangeRate, id: \.currency) { rate in

I also recommend to use plural names for collections, ie. exchangeRates instead of exchangeRate.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for your help! with the T.self I was trying to make the code generic so that it can work with anything, are you saying I should replace the T.self with BitcoinPrice? I have tried to incorporate your suggestions into my code but its saying im missing some parameters `
@HenryHudson There's no problem with your code being generic. But if you want to decode BitcoinPrice you need to make your generic function correctly infer the T type. You can do it by passing some BitcoinPrice variable as the default value.
I see, I have added the defaultValue of BitcoinPrice() now, I am still getting some errors though, think the main one is it says I am missing a parameter from @State private var exchangeRates = BitcoinPrice(), it wants a from parameter in the braces I tried to post the code into this comment but am limited by the number of characters that comments allow
@HenryHudson exchangeRate is an array. When you assign the decoded value: exchangeRate = $0 replace it with exchangeRate = [$0]

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.