1
votes

I'm a beginner in SwiftUI and I'm trying to dev an app for iOS.

I'm trying to use datas from an API request to Text them in my main view struct where I put my different vstack, hstack etc...

So to do that I followed this tutorial: https://www.youtube.com/watch?v=lFE-TJJxxLU

My API response is: https://api.ethermine.org/miner/b5404f020334f52b33012af3587e69305eabee2c/dashboard

So I prepare my model like that:

import Foundation

struct ethermineResponse: Decodable {
    var status : String
    var data : dataDetails
}

struct dataDetails: Decodable {
    var statistics : [statistics]
    var workers : [workers]
    var currentStatistics : currentStatistics
    var settings : settings
}

struct statistics: Decodable {
    var time : Date
    var reportedHashrate : Double
    var currentHashrate : Double
    var validShares : Double
    var invalidShares : Int
    var staleShares : Int
    var activeWorkers : Int
}

struct workers: Decodable {
    var worker : String
    var time : Date
    var lastSeen : Date
    var reportedHashrate : Double
    var currentHashrate : Double
    var validShares : Double
    var invalidShares : Int
    var staleShares : Int
}

struct currentStatistics: Decodable {
    var time : Date
    var lastSeen : Date
    var reportedHashrate : Double
    var currentHashrate : Double
    var validShares : Double
    var invalidShares : Double
    var staleShares : Int
    var activeWorkers : Int
    var unpaid : Double
}

struct settings: Decodable {
    var monitor : Int
    var minPayout : Double
    var email : String
}

And I coded my request:

import Foundation

enum dataError: Error{
    case noDataAvailable
    case canNotProcessData
}

struct ethermineRequest {
    let resourceURL:URL
    
    init(wallet: String) {
        let resourceString = "https://api.ethermine.org/miner/\(wallet)/dashboard"
        guard let resourceURL = URL(string: resourceString) else {fatalError()}
        
        self.resourceURL = resourceURL
    }
    
    func getData(completionHandler: @escaping (Result<ethermineResponse, dataError>) -> Void){
        let task = URLSession.shared.dataTask(with: resourceURL) { data, _, _ in
            guard let jsondata = data else {
                completionHandler(.failure(.noDataAvailable))
                return
            }
            do{
                let ETHData = try JSONDecoder().decode(ethermineResponse.self, from: jsondata)
                
                completionHandler(.success(ETHData))
            }catch{
                print("error")
                
            }

                    }
        task.resume()
    }
}

And in my main view struct I'm trying to text for exemple my "unpaid" value in a VStack like:

VStack{
                    let API = ethermineRequest(wallet: wallet)
                    API.getData { (ethermineResponse) in
                        Text(currentStatistics.unpaid!)
                    }
                    Spacer()
                }

But I have this error in my:

var body: some View

"Failed to produce diagnostic for expression; please file a bug report"

How can I Text for example the unpaid value from my API request ?

Thanks :)

1
Unrelated but print("error") => print("Error: \(error)"), print the error, it might give informations!. - Larme
I tried, now I have more details: Type 'currentStatistics.Type' cannot conform to 'StringProtocol'; only struct/enum/class types can conform to protocols And Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols - DomTorreto

1 Answers

0
votes

Most issues are likely because of how you are naming your objects.

For example,

 var statistics : [statistics]
 var workers : [workers]

How can the complier tell the difference between your variable and your struct

These are even worse because you don't even have the array to make them different.

var currentStatistics : currentStatistics
var settings : settings

Name all you class and struct starting with a capital letter.

var statistics : [Statistics]
var settings : Settings
var currentStatistics : CurrentStatistics

Some tutorials are teaching that other way but notice all of the class, struct, protocol that come from Apple none of them start with a lowercase only var and let

Notice String, Int, Double, Foundation, ContentView they all start with a capital letter.

https://swift.org/documentation/api-design-guidelines/#fundamentals

This line right here could be an issue too. For several reasons.

let API = ethermineRequest(wallet: wallet)
                API.getData { (ethermineResponse) in

1st - You are creating a variable/making a request in the body. That work should be in a class ViewModel the body is only to show things and trigger actions not to do any work.

The body can get reloaded at any time by many triggers and you would be performing that request every time there is a reload.

2nd - unpaid is a Double so to display it you would have to use.

Text(currentStatistics.unpaid!.description)

But the currentStatistics variable should be in a ViewModel not being created as it is being displayed.

I switched all the struct to start with a capital letter. Below is a way to do it but there are many ways that you can accomplish this. Look at the comments.

class EthermineViewModel: ObservableObject {
    @Published var wallet: String = ""{
        didSet{
            getResponse(wallet: wallet)
        }
    }
    @Published var response: EthermineResponse?
    
    private func getResponse(wallet: String) {
        let request = EthermineRequest(wallet: wallet)
        request.getData { (ethermineResponse) in
            switch ethermineResponse{
            case .failure(let error):
                //This should somehow trigger an Alert to let the user know there has been an error
                print(error)
            case .success(let response):
                self.response = response
            }
        }
    }
}
struct EthermineView: View {
    @StateObject var vm: EthermineViewModel = EthermineViewModel()
    let wallet: String = "b5404f020334f52b33012af3587e69305eabee2c"
    var body: some View {
        Text(vm.response?.data.currentStatistics.unpaid.description ?? "nil")
            .onAppear(){
                vm.wallet = wallet
            }
    }
}

struct EthermineView_Previews: PreviewProvider {
    static var previews: some View {
        EthermineView()
    }
}
struct EthermineRequest {
    let resourceURL:URL
    
    init(wallet: String) {
        let resourceString = "https://api.ethermine.org/miner/\(wallet)/dashboard"
        guard let resourceURL = URL(string: resourceString) else {
            fatalError("resourceString == invalid url")
            
        }
        
        self.resourceURL = resourceURL
    }
    
    func getData(completionHandler: @escaping (Result<EthermineResponse, DataError>) -> Void){
        let task = URLSession.shared.dataTask(with: resourceURL) { data, urlResponse, responseError in
            guard let jsondata = data else {
                print(responseError ?? "responseError")
                //Is the absence of data the only type of error you could have?
                completionHandler(.failure(.noDataAvailable))
                return
            }
            do{
                let data = try JSONDecoder().decode(EthermineResponse.self, from: jsondata)
                
                completionHandler(.success(data))
            }catch{
                //this message will be much more useful
                print(error)
                //or
                let nsError = error as NSError
                print(nsError.localizedFailureReason ?? "no failure reason")
                print(nsError.localizedRecoveryOptions ?? "no recovery options")
                
            }
            
        }
        task.resume()
    }
}