Trying to retrieve boolean value from a function in swift - swift

I am trying to get a variable from the function that represents the boolean status of the charge_port_door_open variable, something like this:
let ChargePortStatus = (DATA.response.charge_state.charge_port_door_open)
The function does print a correct boolean, however, 'DATA' cannot be found in scope outside the function.
Edit: Thank you all very much for the help!
This is my first question posted on this website and I wasnt expecting for help to arrive so quickly!
import Foundation
import UIKit
struct Root: Codable {
let response: TEST1
}
struct TEST1: Codable {
let charge_state: TEST2
}
struct TEST2: Codable {
let charge_port_door_open: Bool
}
public func RequestVehicleData() {
let url = URL(string: "https://owner-api.teslamotors.com/api/1/vehicles/:id/vehicle_data")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue( "Bearer \(token)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
let DATA = try! JSONDecoder().decode(Root.self, from: data!)
print(DATA.response.charge_state.charge_port_door_open)
}
task.resume()
}

Since dataTask is asynchronous, you have to modify your method to return a value in a closure. A simple version without any error handling can look like this:
public func requestVehicleData(completion: #escaping ((Bool?) -> Void) {
let url = URL(string: "https://owner-api.teslamotors.com/api/1/vehicles/:id/vehicle_data")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue( "Bearer \(token)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let responseData = data, let response = try? JSONDecoder().decode(Root.self, from: responseData) {
completion(response.response.charge_state.charge_port_door_open)
} else {
completion(nil)
}
}
task.resume()
}
You can also play around with the Swift Concurrency and user new async/await API for the same purpose.
P.S. When you have a chance, take a look at some basics and API Design Guideline.

Two simple approaches (if you don't want the call-site to have to use a completion block - which is also a perfectly good solution described in Maksym's answer) are..
perform any actions you need to perform with the value inside of the closure
create an optional property with a didSet on whatever owns RequestVehicleData. That way you can set the value within the closure and trigger the desired actions. An example for this would be:
class VehicleDataManager {
private var root: Root? {
didSet {
doSomething(with: root)
}
}
public func RequestVehicleData() {
let url = URL(string: "https://owner-api.teslamotors.com/api/1/vehicles/:id/vehicle_data")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue( "Bearer \(token)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
root = try! JSONDecoder().decode(Root.self, from: data!)
print(DATA.response.charge_state.charge_port_door_open)
}
task.resume()
}
}
The value is fetched asynchronously, so it will only be safely accessible in places that are guaranteed to get run after it has been returned.
Side note: I know this might have just been for the sake of a "quick and dirty" test function, but I would highly recommend not getting in the habit of force-unwrapping values that very well could be nil

Related

Instance method requires that 'classname' conform to 'Decodable'

I'm trying to decode a JSON response as a custom type, that I believe conforms to Decodable.
These are the codable structs that I am using
struct ResultSet: Codable {
var name: String
var headers: [String]
var rowSet: [String]
}
struct Scoreboard: Codable {
var resultSets: [ResultSet]
}
And this is the code I'm using to get the JSON from the response
func loadNbaScoreboardData<Scoreboard>() -> Scoreboard {
//var data1: Data
let formatter = DateFormatter()
formatter.dateFormat = "MM/dd/yyyy"
let formattedDate = formatter.string(from: Date())
let url = URL(string: "https://stats.nba.com/stats/scoreboard/?GameDate=\(formattedDate)&LeagueID=00&DayOffset=100")
var request = URLRequest(url: url!)
request.httpMethod = "GET"
request.setValue("stats.nba.com", forHTTPHeaderField: "host")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("stats", forHTTPHeaderField: "x-nba-stats-origin")
request.setValue("x-nba-stats-origin", forHTTPHeaderField: "Referer")
var retData: Scoreboard
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else{ return }
do {
let decodedData = try JSONDecoder().decode(Scoreboard.self, from: data)
retData = decodedData
} catch {
fatalError(error)
}
}.resume()
return retData
}
The error I get is Instance method 'decode(_:from:)' requires that 'Scoreboard' conform to 'Decodable'
I'm following the dev documentation here too https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types
What am I doing wrong here?
EDIT: The Scoreboard struct can't be found. I've added the full method
In your code Scoreboard is a generic type (not the concrete type Scoreboard). You can fix the error by adding Codable conformance
func loadNbaScoreboardData<Scoreboard: Codable>() -> Scoreboard {
But the code won't work anyway because you cannot return something from an asynchronous task.
I recommend to make the function async
func loadNbaScoreboardData() async throws -> Scoreboard {
//var data1: Data
let formatter = DateFormatter()
formatter.dateFormat = "MM/dd/yyyy"
let formattedDate = formatter.string(from: Date())
let url = URL(string: "https://stats.nba.com/stats/scoreboard/?GameDate=\(formattedDate)&LeagueID=00&DayOffset=100")
var request = URLRequest(url: url!)
request.httpMethod = "GET"
request.setValue("stats.nba.com", forHTTPHeaderField: "host")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("stats", forHTTPHeaderField: "x-nba-stats-origin")
request.setValue("x-nba-stats-origin", forHTTPHeaderField: "Referer")
let (data, _ ) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode(Scoreboard.self, from: data)
}
It was the method declaration
It should be
func loadNbaScoreboardData() -> Scoreboard {
// code
}

How to use POST request to Firebase Firestore from swift?

I would like to use the Firestore REST API from Swift, because I am using Siri Shortcut Intents, where I am not able to use the native SDK.
What I have tried so far is to create an URLSession with "POST" httpmethod, but no luck. I have been able successfully to create document to use the form found on firestore website. But I could make successful Swift version of it.
Here is the code I have tried:
private func addTask() {
let parent = "projects/reality-kanban/databases/(default)/documents/l3VXrtTLoz11VGn60ott"
let collectionId = "A33XrtfL2ea3dG340era"
let urlString = "https://firestore.googleapis.com/v1/\(parent)/\(collectionId)"
let requestBody = DocumentBody(name: parent, fields: RequestTask(description: "test")) // it is a codable struct
let jsonData = try! JSONEncoder().encode(requestBody)
print(String(data: jsonData, encoding: .utf8)!)
let url = URL(string: urlString)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = jsonData
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
print("Invalid Response received from the server") // this is what I get
return
}
}
task.resume()
}
This is the error I get: Invalid Response received from the server (400)
Add --debug to the command you are running to corroborate if you have set the right project.

Swift HTTP POST request with Combine

In my app, I am using the Combine framework to make network requests and it works fine for GET requests. But I am running into this issue with POST requests.
The following code (without Combine) works fine:
let data = ["statusTime": DateFormatter.iso8601Full.string(from: Date())]
let requestBody = try? JSONSerialization.data(withJSONObject: data, options: [])
let baseURL = "my-api.amazonaws.com"
let endpoint = "/my/endpoint"
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = baseURL
urlComponents.path = endpoint
let url = urlComponents.url!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = requestBody
request.addValue(authorizationToken, forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("error: \(error)")
return
}
print("Response: \((response as! HTTPURLResponse).statusCode)")
}
task.resume()
I get a 200 response code from the above code.
The same code with Combine:
let data = ["statusTime": DateFormatter.iso8601Full.string(from: Date())]
let requestBody = try? JSONSerialization.data(withJSONObject: data, options: [])
let baseURL = "my-api.amazonaws.com"
let endpoint = "/my/endpoint"
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = baseURL
urlComponents.path = endpoint
let url = urlComponents.url!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = requestBody
request.addValue(authorizationToken, forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let cancellable = URLSession.shared
.dataTaskPublisher(for: request)
.print()
.sink(receiveCompletion: {
if let error = $0.error {
print("Failure: \(error)")
}
}, receiveValue: {
print("\($0)")
})
Here is the output I receive:
receive subscription: (DataTaskPublisher)
request unlimited
receive cancel
Seems like the request with the Combine framework is somehow getting cancelled? I used a network tracing app and I can see the first request (without Combine), but not the second request.
I have looked at numerous posts and documentation, but can't see what's wrong with my code. What am I missing? Thanks in advance.
I see you are creating the cancellable, but are you keeping a strong reference to it? It looks like you are creating the request, and then the cancellable goes out of scope and is deallocated, which cancels the request.
Declare something like this in your class:
var cancellables = Set<AnyCancellable>()
and then you implicitly store the cancellable as:
URLSession.shared // You don't hold on to the returned cancellable here
.dataTaskPublisher(for: request)
.print()
.sink(receiveCompletion: {
if let error = $0.error {
print("Failure: \(error)")
}
}, receiveValue: {
print("\($0)")
})
.store(in: &cancellables) // This puts the cancellable into the variables so it stays in scope

Add headers in request but still have a 403

I am trying to request an API in order to get response data. I need to add to headers in the request what I have done using request.addValue(""). It works fine when I am using postman. But I don't why I got no data with Xcode. The response is 403 Forbidden
class Service {
private static let sUrl = URL(string: "https://bff-mobile-dev.XXXXX.com/demands/filter")!
static func getData() {
var request = URLRequest(url: sUrl)
request.httpMethod = "POST"
let session = URLSession(configuration: .default)
request.addValue("Accept-Version", forHTTPHeaderField: "3.0.0")
request.addValue("X-Request-Id", forHTTPHeaderField: "057BC3BD-46E1-4125-9F3B-23805CA3132F")
let task = session.dataTask(with: request) { (data, response, error) in
if let data = data, error == nil {
if let response = response as? HTTPURLResponse {
print(response)
}
}
}
task.resume()
}
}
I believe your key value pair is mixed up when you added the header fields.
request.setValue(value,forHTTPHeaderField: "HeaderFieldName")
Adding like this works.
I think that you inverted values of you header; they should be like this
request.addValue("3.0.0", forHTTPHeaderField: "Accept-Version")
request.addValue("057BC3BD-46E1-4125-9F3B-23805CA3132F", forHTTPHeaderField: "X-Request-Id")
Try something like below
var headerData:[String:String] = [:]
headerData["Accept-Version"] = "3.0.0"
headerData["X-Request-Id"] = "057BC3BD-46E1-4125-9F3B-23805CA3132F"
request.allHTTPHeaderFields = headerData

Post data using URLSession

When i try from Alamofire then it work fine but when i try to solve from URLSESSION Swift 4 then i got wrong response.
I checked in postman and it's response was right.
Parameter Description:
I have a key "data" whose value is another dictionary ["answer1":"1","answer2":"2","answer3":"3"]. Need to post this.
Wrong Reposnse = {"message = "Invalid data."; response = failure;}"
Right Reposnse = {"response":"success","message":"Data Inserted”}.
func postData()
{
let BASEURLS = "http://sebamedretail.techizer.in/babystore_api/question_data"
let parameter = ["data":["answer1":"1","answer2":"2","answer3":"3"]]
let session = URLSession.shared
var request = URLRequest.init(url: URL.init(string: BASEURLS)!)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do
{
request.httpBody = try JSONSerialization.data(withJSONObject:parameter, options: [])
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
do{
let responseArr = try! JSONSerialization.jsonObject(with: data!, options: [])
}
})
task.resume()
}
catch
{}
}
Everything seems fine, maybe you should check the way you extract the JSON in your code, if it's PHP, here could be a solution: https://stackoverflow.com/a/18867369/7452015