I have a method that returns a bool (defaulted to false) and uses Alamofire to call an API which return a string. However, as the API request is asynchronous, the method always completes and returns false before the async request is finished.
How can I wait till the API call is 'actually' complete in order to return the correct result?
I've tried putting the return statement within the .success and .failure blocks but that causes an error
func validateCredentials() -> Bool{
var apiResult = false
Alamofire.request("http://myURL?pUser=\(usernameTextField.text!)&pPwd=\(passwordTextField.text!)&pCheckIt=y")
.responseString {
response in
switch response.result{
case .success:
apiResult = true
case .failure:
apiResult = false
}
}
ProgressHUD.show("Please wait...")
return apiResult . <==== Always return false
}
You need to put escaping completion block Like below.
func validateCredentials(completion:#escaping(Bool) -> Void) {
ProgressHUD.show("Please wait...")
Alamofire.request("http://myURL?pUser=\(usernameTextField.text!)&pPwd=\(passwordTextField.text!)&pCheckIt=y")
.responseString {
ProgressHUD.hide()
response in
switch response.result{
case .success:
completion(true)
case .failure:
completion(false)
}
}
}
Related
I have a question on throwing errors and returning values from ResponseString Alamofire. In fact, I think this is a problem with specified closures as parameter functions
func makeRequest() async throws -> String {
AF.request("https://example.com")
.validate()
.responseString { response in
switch response.result {
case .success(let id): return id
case .failure(let error): throw APIErrors.alamoFireError(error)
}
}
}
How would I go about
Alamofire's response handlers (the various response* methods) don't support additional error handling. But that's not your fundamental issue, as you're trying to use a completion handler in an async context, which isn't going to work anyway. I suggest you use Alamofire's async handling instead.
func makeRequest() async throws -> String {
let result = await AF.request("https://example.com")
.validate()
.result
switch result {
case .success(let id): return id
case .failure(let error): throw APIErrors.alamoFireError(error)
}
}
I have an app where I used RxSwift for my networking by extending ObservableType this works well but the issue I am having now is when I make an API request and there is an error, I am unable to show the particular error message sent from the server. Now how can I get the particular error response sent from the server
extension ObservableType {
func convert<T: EVObject>(to observableType: T.Type) -> Observable<T> where E: DataRequest {
return self.flatMap({(request) -> Observable<T> in
let disposable = Disposables.create {
request.cancel()
}
return Observable<T>.create({observer -> Disposable in
request.validate().responseObject { (response: DataResponse<T>) in
switch response.result {
case .success(let value):
if !disposable.isDisposed {
observer.onNext(value)
observer.onCompleted()
}
case .failure(let error):
if !disposable.isDisposed {
observer.onError(NetworkingError(httpResponse: response.response,
networkData: response.data, baseError: error))
observer.onCompleted()
}
}
}
return disposable
})
})
}
}
let networkRetryPredicate: RetryPredicate = { error in
if let err = error as? NetworkingError, let response = err.httpResponse {
let code = response.statusCode
if code >= 400 && code < 600 {
return false
}
}
return true
}
// Use this struct to pass the response and data along with
// the error as alamofire does not do this automatically
public struct NetworkingError: Error {
let httpResponse: HTTPURLResponse?
let networkData: Data?
let baseError: Error
}
response from the server could be
{
"status" : "error",
"message" : " INSUFFICIENT_FUNDS"
}
or
{
"status" : "success",
"data" : " gghfgdgchf"
}
my response is handled like this
class MaxResponse<T: NSObject>: MaxResponseBase, EVGenericsKVC {
var data: T?
public func setGenericValue(_ value: AnyObject!, forUndefinedKey key: String) {
switch key {
case "data":
data = value as? T
default:
print("---> setGenericValue '\(value)' forUndefinedKey '\(key)' should be handled.")
}
}
public func getGenericType() -> NSObject {
return T()
}
}
the error is
return ApiClient.session.rx.request(urlRequest: MaxApiRouter.topupWall(userId: getUser()!.id!, data: body))
.convert(to: MaxResponse<Wall>.self)
In the official Alamofire docs it is mentioned that validate(), without any parameters:
Automatically validates status code within 200..<300 range, and that
the Content-Type header of the response matches the Accept header of
the request, if one is provided.
So if you do not include Alamofire's validate() you are saying that no matter the status code, if the request did get through, you will consider it successful, so that's why it shows nothing in the failure block.
However if you prefer to use it, yes, it will give you an ResponseValidationFailureReason error, but you still have access to the response.data. Try printing it, you should see the expected error response from the server:
if let responseData = response.data {
print(String(data: responseData, encoding: .utf8))
}
I have a main function (buttonPressed), that should print out a Jsonfile.
func buttonPressed {
var jasonFile = JSON()
jasonFile = getInfo(parA: 5, parB: 10) //1. takes some time
print(jsonFile) //prints before the JSON is get from the internet
}
To get the Jsonfile I made a function, which accepts parameters and downloads the information and returns the Jsonfile.
Of course this takes quite a while so the code in myFunction is already executed even though it haven't received the data.
(I am using Alamofire and SwiftyJSON)
func getInfo(parA: Int, parB: Int) -> [Int] {
var thisJsonFile = JSON()
Alamofire.request(url, method: .get).validate().responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
thisJsonFile = json
case .failure(let error):
print(error)
}
}
return thisJsonFile //returns an empty File
}
Is there a way to return the JsonFile after it has been finished loading?
Thank you for your answers, I found a solution, that is workin for me:
I additionally installed Alamofire_Synchronous.
The codefor my function is now is now:
func getInfo(parA: Int, parB: Int) -> [Int] {
let response = Alamofire.request(url).responseJSON()
switch response.result {
case .success(let value):
let json = JSON(value)
return json
case .failure(let error):
print(error)
return JSON()
}
}
I would like to accept classic status code range (2XX) but also some extra error status code. So, how can i do this with the validate method of Alamofire Request?
Something like that:
Alamofire.request(self)
.validate(statusCode: [ 200..<300 , 403 ])
.responseJSON { response in
switch response.result {
case .Success(let JSON):
...
case .Failure(let error):
...
}
})
Alamofire accepts a Range<Int> parameter with acceptable codes. A range requires that all elements are consecutive, so you'll have to code your own validator. This should work:
.validate { _, response in
let acceptableStatusCodes: [Range<Int>] = [200..<300, 403...403]
if acceptableStatusCodes.map({$0.contains(response.statusCode)}).reduce(false, combine: {$0 || $1}) {
return .Success
} else {
let failureReason = "Response status code was unacceptable: \(response.statusCode)"
return .Failure(Error.errorWithCode(.StatusCodeValidationFailed, failureReason: failureReason))
}
}
You can also declare it in a Request extension for better code reusability:
extension Request {
func validateStatusCode() -> Request {
return self.validate { _, response in
let acceptableStatusCodes: [Range<Int>] = [200..<300, 403...403]
if acceptableStatusCodes.map({$0.contains(response.statusCode)}).reduce(false, combine: {$0 || $1}) {
return .Success
} else {
let failureReason = "Response status code was unacceptable: \(response.statusCode)"
return .Failure(Error.errorWithCode(.StatusCodeValidationFailed, failureReason: failureReason))
}
}
}
}
And call it like this:
Alamofire.request(self)
.validateStatusCode()
.responseJSON { response in
switch response.result {
case .Success(let JSON):
...
case .Failure(let error):
...
}
})
I think this is a pretty common use case, but I couldn't find any best way to achieve it.
Some parts of my ios application require login. How do I achieve the following pattern using Alamofire and swift.
.request (.GET 'login_required_endpoint')
.responsejson(if statusCode == 401){
login()
continue with GETing
login_required_endpoint
What is the best way to achieve this.
Make a request
If server responds with 401(Unauthorized)
Ask user to login after saving all the request payload for previous request
After successful login, continue with request in [1] with payload saved
(I realize it is open-ended, but any help on how to make progress would be highly appreciated)
With the below, you can call handleRequest instead of Alamofire's request
import Alamofire
import PromiseKit
enum ServerError: ErrorType {
case Unauthorized
// add others as necessary
}
func handleRequest(method: Alamofire.Method, _ URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil) -> Promise<AnyObject> {
return request(method, URLString, parameters: parameters).recover { error -> AnyObject in
switch error {
case ServerError.Unauthorized:
return login().then {
request(method, URLString, parameters: parameters)
}
default:
throw error
}
}
}
private func request(method: Alamofire.Method, _ URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil) -> Promise<AnyObject> {
return Promise { fulfill, reject in
Alamofire.request(method, URLString, parameters: parameters).responseJSON { response in
switch response.result {
case .Success(let json):
fulfill(json)
case .Failure(let error):
if response.response?.statusCode == 401 {
reject(ServerError.Unauthorized)
}
else {
reject(error)
}
}
}
}
}
private func login() -> Promise<Void> {
// do what you need here...
return Promise()
}