Completion handler not being called - Alamofire / stripe - swift

My completion handler is never called.
This completion handler is written by stripe:
(end of step 1)
https://stripe.com/docs/mobile/ios/standard#ephemeral-key
Ive tried simplifying the function as much as possible (i made it more complex before), and putting a completion() in the end of every .case .
When i shift click on the STPJSONResponseCompletionBlock it tells me the parameters required are this:
jsonResponse
The JSON response, or nil if an error occured.
error
The error that occurred, if any.
if i call completion(nil, nil) i can crash my app, and it gives me this error:
2019-07-30 10:38:42.917984+0100 foodfactory[27595:2238502] *** Assertion failure in -[STPEphemeralKeyManager _createKey],
2019-07-30 10:38:42.929757+0100 foodfactory[27595:2238502] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not parse the ephemeral key response following protocol STPCustomerEphemeralKeyProvider. Make sure your backend is sending the unmodified JSON of the ephemeral key to your app. For more info, see https://stripe.com/docs/mobile/ios/standard#prepare-your-api'
But, if i pass in json as in the example below, it simply never calls..
Furthermore, i have copied the NodeJS code which sent the json to this completion handler as closely as possible (im on google cloud functions so there had to be some changes, and i checked my JSON im receiving and the ephemeral key is there, and i can access it if i write my own method to decode the json).
Also, as i can make it crash, i guess the completion handler must be being called?
This code calls the function with completion handler:
func changePaymentOptionsButtonTapped() {
// Setup customer context
let apiVersion = String(Stripe.version())
MyAPIClient.sharedClient.createCustomerKey(withAPIVersion: apiVersion) { (ephemeralKeyJson, err) in
print("ThisIsNeverCalled")
if let ephemeralKey = ephemeralKeyJson?["id"] as? String {
} else {
print("Error parsing JSON from customer creation: See: MyAPIClient - func createCustomerKey")
}
}
}
The function with completion handler:
func createCustomerKey(withAPIVersion apiVersion: String, completion: #escaping STPJSONResponseCompletionBlock) {
guard let customerId = KeychainWrapper.standard.string(forKey: Stripe_Customer_Id) else {
print("Keychain wrapper not retrieving stripe customer Id at MyAPIClient")
// completion(nil, nil)
return
}
let url = "https://us-central1-foodfactory-813ab.cloudfunctions.net/request_ephemeral_key"
Alamofire.request(url, method: .post, parameters: [
"api_version": apiVersion,
"customerId": customerId,
])
.validate(statusCode: 200..<300)
.responseJSON { responseJSON in
switch responseJSON.result {
case .success(let json):
completion(json as? [String: AnyObject], nil)
case .failure(let error):
print("Error creating customer Key (retrieving ephemeral key) with Alamofire. See: MyAPIClient - func createCustomerKey")
completion(nil, error)
}
}
}
Anyone got any suggestions? My guess is it's something to do with what im passing into the completion handler.

There's definitely an error here:
json as? [String: AnyObject]
change it to
json as? [String: Any]
The reason for it is that a JSON dictionary can contain things that aren't AnyObject - for instance, String is a struct, Int or Bool aren't objects either

Related

Swift program never enters CompletionHandler for a dataTask

I am in the process of implementing a REST API with Swift. Of course, part of this API is using HTTP requests to retrieve and send data.
Full disclosure, I am inexperienced with Swift and am using this as a learning project to get my feet wet, so to speak. But it's turned into much more of a difficult project than I anticipated.
In implementing the first get method, I have (finally) gotten rid of all the compilation errors. However, when I call the function which utilizes the URLRequest, URLSession, dataTask, etc, it is never entered.
Upon debugging the program, I can watch the program execution reach the CompletionHandler, and skip over it right to "task.resume()."
A similar construction works in a Swift Playground, but does not work in the actual project proper.
So far I have tried a few things, namely making the function access a class instance variable, in hopes that that would force it to execute. But it does not.
I think the issue may be dealing with synchronicity, and perhaps I need to use a Semaphore, but I want to make sure I'm not missing anything obvious first.
import Foundation
/**
A class to wrap all GET and POST requests, to avoid the necessity of repeatedly writing request code in each API method.
*/
class BasicRequest {
private var url: URL
private var header: [String: String]
private var responseType: String
private var jsonResponse: Any?
init(url: URL, header: [String: String], responseType: String) {
self.url = url
self.header = header
self.responseType = responseType
} //END INIT
public func requestJSON() -> Any {
// Create the URLRequest object, and fill the header with the header fields as provided.
var urlRequest = URLRequest(url: self.url)
for (value, key) in self.header {
urlRequest.addValue(value, forHTTPHeaderField: key)
}
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
print("Entered the completion handler")
if error != nil {
return
}
guard let httpResponse = response as? HTTPURLResponse, 200 == httpResponse.statusCode else {
print("HTTP Request unsuccessful")
return
}
guard let mime = response?.mimeType, mime == "application/json" else {
print("Not a JSON response")
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: [])
print(json)
self.jsonResponse = json
} catch {
print("Could not transform to JSON")
return
}
}
task.resume()
return "Function has returned"
} //END REQUESTJSON
}
The expected result would be returning a JSON object, however that does not seem to be the case.
With respect to error messages, I get none. The only log I get in the debugger is the boilerplate "process exited with code 0."
To be truthful, I'm at a loss with what is causing this not to work.
It appears you're writing this in a command-line app. In that case the program is terminating before the URLRequest completes.
I think the issue may be dealing with synchronicity, and perhaps I need to use a Semaphore, but I want to make sure I'm not missing anything obvious first.
Exactly.
The typical tool in Swift is DispatchGroup, which is just a higher-level kind of semaphore. Call dispatchGroup.enter() before starting the request, and all dispatchGroup.leave() at the end of the completion handler. In your calling code, include dispatchGroup.wait() to wait for it. (If that's not clear, I can add code for it, but there are also a lot of SO answers you can find that will demonstrate it.)

Getting error when trying to use Result type with delegate

Im tring to make a network call and instead of using callback I try to use delegate instead.using Result type where .Sucsess is T: Decodable and .failure is Error. passing my model in the .Sucsess is working but when trying to pass an error I get a compile error "Generic parameter 'T' could not be inferred" what am I missing ?
protocol NetworkServiceDelegate: class {
func decodableResponce<T: Decodable>(_ result: Result<T, NetworkError>)
}
let dataTask:URLSessionTask = session.dataTask(with: url) { (dataOrNil, responceOrNil, errOrNil) in
if let error = errOrNil {
switch error {
case URLError.networkConnectionLost,URLError.notConnectedToInternet:
print("no network connection")
self.delegate?.decodableResponce(Result.failure(.networkConnectionLost))
case URLError.cannotFindHost, URLError.notConnectedToInternet:
print("cant find the host, could be to busy, try again in a little while")
case URLError.cancelled:
// if cancelled with the cancelled method the complition is still called
print("dont bother the user, we're doing what they want")
default:
print("error = \(error.localizedDescription)")
}
return
}
guard let httpResponce:HTTPURLResponse = responceOrNil as? HTTPURLResponse
else{
print("not an http responce")
return
}
guard let dataResponse = dataOrNil,
errOrNil == nil else {
print(errOrNil?.localizedDescription ?? "Response Error")
return }
do{
//here dataResponse received from a network request
let decoder = JSONDecoder()
let modelArray = try decoder.decode([Movie].self, from:
dataResponse) //Decode JSON Response Data
DispatchQueue.main.async {
self.delegate?.decodableResponce(Result.success(modelArray))
}
} catch let parsingError {
print("Error", parsingError)
}
print("http status = \(httpResponce.statusCode)")
print("completed")
}
this line generates the error, it dosnt metter if I pass my enum that cumfirms to Error or trying to pass the error from the dataTask
self.delegate?.decodableResponce(Result.failure(.networkConnectionLost))
Well, you have two problems, having to do with the question "what type is this?" Swift is very strict about types, so you need to get clear about that.
.networkConnectionLost is not an Error. It is an error code. You need to pass an Error object to a Result when you want to package up the error. For example, URLError(URLError.networkConnectionLost) is an Error.
The phrase Result<T, NetworkError> makes no sense. Result is already a generic. Your job is to resolve the generic that it already is. You do that by specifying the type.
So for example, you might declare:
func decodableResponce(_ result: Result<Decodable, Error>)
It is then possible to say (as tests):
decodableResponce(.failure(URLError(URLError.networkConnectionLost)))
or (assuming Movie is Decodable):
decodableResponce(.success([Movie()]))
That proves we have our types right, and you can proceed to build up your actual code around that example code.

Swift 4 - How can I call a piece of code which returns out of the function in multiple functions without duplicating code?

I am working with a poorly designed API (I don't have control over it) where even if the access token is expired, it still returns a HTTP success code but includes the 401 Unauthorized in the actual response body. So simply checking the HTTP status code isn't sufficient and I need to check the actual response.
I am making many network requests in my app to this API and when I receive the response, I need to first check whether the response is an array or a dictionary. If array, then we are good. If it's a dictionary, then I need to check the "error" field in the response dictionary which will have the 401 Unauthorized.
So every time I receive the JSON response, I have the following piece of code to return out of the function if it's an error dictionary:
guard !(myJSON is NSDictionary) && (myJSON as! NSDictionary).value(forKey: "error") != nil else {
print("Error: ", MyAppError.accessTokenExpired)
return
}
I am wondering if there is a way to prevent duplicating this piece of code in every network request function I have? Can I have this somewhere and simply call it without duplicating these lines of code each time?
Wrap it in a function like this one
func isErrorResponse(_ response: Any) -> Bool {
if let dict = response as? [String: Any], let error = dict["error"] {
print("Error: \(MyAppError.accessTokenExpired)")
return true
}
return false
}
You should use the swift data types if you are using Swift language. Although if myJSON validates with Dictionary then it will definitely be validated with [String: Any].
Create function like:
func isValidResponse(_ json: Any) -> Bool {
guard let jsonDict = json as? [String: Any], let let error = dict["error"] else { return true }
print("Error: \(error.localizedDescription)")
return false
}
Use it as:
guard YourClass.isValidResponse(myJSON) else { return }
// Valid Response - Code here...

Best way to handle errors from async closures in Swift 2?

I'm using a lot of async network request (btw any network request in iOS need to by async) and I'm finding way to better handle errors from Apple's dataTaskWithRequest which not supports throws.
I have code like that:
func sendRequest(someData: MyCustomClass?, completion: (response: NSData?) -> ()) {
let request = NSURLRequest(URL: NSURL(string: "http://google.com")!)
if someData == nil {
// throw my custom error
}
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
// here I want to handle Apple's error
}
task.resume()
}
I need to parse my possible custom errors and handle possible connection errors from dataTaskWithRequest. Swift 2 introduced throws, but you can't throw from Apple's closure because they have no throw support and running async.
I see only way to add to my completion block NSError returning, but as I know using NSError is old-style Objective-C way. ErrorType can be used only with throws (afaik).
What's the best and most modern method to handle error when using Apple network closures? There is no way no use throws in any async network functions as I understand?
there are many ways you can solve this, but i would recommend using a completion block which expects a Result Enum. this would probably be the most 'Swift' way.
the result enum has exactly two states, success and error, which a big advantage to the usual two optional return values (data and error) which lead to 4 possible states.
enum Result<T> {
case Success(T)
case Error(String, Int)
}
Using the result enum in a completion block finishes the puzzle.
let InvalidURLCode = 999
let NoDataCode = 998
func getFrom(urlString: String, completion:Result<NSData> -> Void) {
// make sure the URL is valid, if not return custom error
guard let url = NSURL(string: urlString) else { return completion(.Error("Invalid URL", InvalidURLCode)) }
let request = NSURLRequest(URL: url)
NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
// if error returned, extract message and code then pass as Result enum
guard error == nil else { return completion(.Error(error!.localizedDescription, error!.code)) }
// if no data is returned, return custom error
guard let data = data else { return completion(.Error("No data returned", NoDataCode)) }
// return success
completion(.Success(data))
}.resume()
}
because the return value is a enum, you should switch off of it.
getFrom("http://www.google.com") { result in
switch result {
case .Success(let data):
// handle successful data response here
let responseString = String(data:data, encoding: NSASCIIStringEncoding)
print("got data: \(responseString)");
case .Error(let msg, let code):
// handle error here
print("Error [\(code)]: \(msg)")
}
}
another solution would be to pass two completion blocks, one for success and one for error. something along the lines of:
func getFrom(urlString: String, successHandler:NSData -> Void, errorHandler:(String, Int) -> Void)
It's very similar to Casey's answer,
but with Swift 5, now we have Result (generic enumeration) implementation in standard library,
//Don't add this code to your project, this has already been implemented
//in standard library.
public enum Result<Success, Failure: Error> {
case success(Success), failure(Failure)
}
It's very easy to use,
URLSession.shared.dataTask(with: url) { (result: Result<(response: URLResponse, data: Data), Error>) in
switch result {
case let .success(success):
handleResponse(success.response, data: success.data)
case let .error(error):
handleError(error)
}
}
https://developer.apple.com/documentation/swift/result
https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md
There's an elegant approach utilising a JavaScript-like Promise library or a Scala-like "Future and Promise" library.
Using Scala-style futures and promises, it may look as follows:
Your original function
func sendRequest(someData: MyCustomClass?, completion: (response: NSData?) -> ())
may be implemented as shown below. It also shows, how to create a promise, return early with a failed future and how to fulfill/reject a promise:
func sendRequest(someData: MyCustomClass) -> Future<NSData> {
guard let url = ... else {
return Future.failure(MySessionError.InvalidURL) // bail out early with a completed future
}
let request = ... // setup request
let promise = Promise<NSData>()
NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
guard let error = error else {
promise.reject(error) // Client error
}
// The following assertions should be true, unless error != nil
assert(data != nil)
assert(response != nil)
// We expect HTTP protocol:
guard let response = response! as NSHTTPURLResponse else {
promise.reject(MySessionError.ProtocolError) // signal that we expected HTTP.
}
// Check status code:
guard myValidStatusCodeArray.contains(response.statusCode) else {
let message: String? = ... // convert the response data to a string, if any and if possible
promise.reject(MySessionError.InvalidStatusCode(statusCode: response.statusCode, message: message ?? ""))
}
// Check MIME type if given:
if let mimeType = response.MIMEType {
guard myValidMIMETypesArray.contains(mimeType) else {
promise.reject(MySessionError.MIMETypeNotAccepted(mimeType: mimeType))
}
} else {
// If we require a MIMEType - reject the promise.
}
// transform data to some other object if desired, can be done in a later, too.
promise.fulfill(data!)
}.resume()
return promise.future!
}
You might expect a JSON as response - if the request succeeds.
Now, you could use it as follows:
sendRequest(myObject).map { data in
return try NSJSONSerialization.dataWithJSONObject(data, options: [])
}
.map { object in
// the object returned from the step above, unless it failed.
// Now, "process" the object:
...
// You may throw an error if something goes wrong:
if failed {
throw MyError.Failed
}
}
.onFailure { error in
// We reach here IFF an error occurred in any of the
// previous tasks.
// error is of type ErrorType.
print("Error: \(error)")
}

AlamoFire GET api request not working as expected

I am trying to get learn how to use AlamoFire and I am having trouble.
My method so far is as follows:
func siteInfo()->String?{
var info:NSDictionary!
var str:String!
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {(request, response, JSON, error) in
info = JSON as NSDictionary
str = info["access_key"] as String
//return str
}
return str
}
This returns nil which is a problem. From what I have read here, this is because the request can take a while so the closure doesn't execute till after the return. The suggested solution of moving the return into the closure does not work for me and the compiler just yells (adding ->String after (request,response,JSON,error) which gives "'String' is not a subtype of void"). Same goes for the other solution provided.
Any ideas? Even some source code that is not related to this problem, that uses AlamoFire, would be helpful.
Thanks!
One way to handle this is to pass a closure (I usually call it a completionHandler) to your siteInfo function and call that inside Alamofire.request's closure:
func siteInfo(completionHandler: (String?, NSError?) -> ()) -> () {
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {
(request, response, JSON, error) in
let info = JSON as? NSDictionary // info will be nil if it's not an NSDictionary
let str = info?["access_key"] as? String // str will be nil if info is nil or the value for "access_key" is not a String
completionHandler(str, error)
}
}
Then call it like this (don't forget error handling):
siteInfo { (str, error) in
if str != nil {
// Use str value
} else {
// Handle error / nil value
}
}
In the comments you asked:
So how would you save the info you collect from the get request if you
can only do stuff inside the closure and not effect objects outside of
the closure? Also, how to keep track to know when the request has
finished?
You can save the result of the get request to an instance variable in your class from inside the closure; there's nothing about the closure stopping you from doing that. What you do from there really depends on, well, what you want to do with that data.
How about an example?
Since it looks like you're getting an access key form that get request, maybe you need that for future requests made in other functions.
In that case, you can do something like this:
Note: Asynchronous programming is a huge topic; way too much to cover here. This is just one example of how you might handle the data you get back from your asynchronous request.
public class Site {
private var _accessKey: String?
private func getAccessKey(completionHandler: (String?, NSError?) -> ()) -> () {
// If we already have an access key, call the completion handler with it immediately
if let accessKey = self._accessKey {
completionHandler(accessKey, nil)
} else { // Otherwise request one
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {
(request, response, JSON, error) in
let info = JSON as? NSDictionary // info will be nil if it's not an NSDictionary
let accessKey = info?["access_key"] as? String // accessKey will be nil if info is nil or the value for "access_key" is not a String
self._accessKey = accessKey
completionHandler(accessKey, error)
}
}
}
public func somethingNeedingAccessKey() {
getAccessKey { (accessKey, error) in
if accessKey != nil {
// Use accessKey however you'd like here
println(accessKey)
} else {
// Handle error / nil accessKey here
}
}
}
}
With that setup, calling somethingNeedingAccessKey() the first time will trigger a request to get the access key. Any calls to somethingNeedingAccessKey() after that will use the value already stored in self._accessKey. If you do the rest of somethingNeedingAccessKey's work inside the closure being passed to getAccessKey, you can be sure that your accessKey will always be valid. If you need another function that needs accessKey, just write it the same way somethingNeedingAccessKey is written.
public func somethingElse() {
getAccessKey { (accessKey, error) in
if accessKey != nil {
// Do something else with accessKey
} else {
// Handle nil accessKey / error here
}
}
}