I am trying to extract a post request so it can be re-used and keep my code as DRY as possible bu I'm struggling a little. I started off with:
func createAccount() {
let manager = AFHTTPSessionManager()
let dob = self.dobTextField.text!.components(separatedBy: "/")
let URL = "https://splitterstripeservertest.herokuapp.com/account"
let params = [
"first_name": firstNameTextField.text!.trim(),
"last_name": lastNameTextField.text!.trim(),
"line1": addressLine1TextField.text!.trim(),
"city": cityTextField.text!.trim(),
"postal_code": postCodeTextField.text!.trim(),
"email": emailTextField.text!.trim(),
"day": UInt(dob[0])! as UInt,
"month": UInt(dob[1])! as UInt,
"year": UInt(dob[2])! as UInt] as [String : Any]
manager.requestSerializer = AFHTTPRequestSerializer()
manager.responseSerializer = AFHTTPResponseSerializer()
manager.post(URL, parameters: params, progress: nil, success: {(_ task: URLSessionDataTask, _ responseObject: Any) -> Void in
do {
let response = try JSONSerialization.jsonObject(with: responseObject as! Data, options: .mutableContainers) as? [String: Any]
self.stripeAccountID = response?["id"] as! String
self.stopAnimating()
self.goToFinalStage()
} catch {
print("Serialising new account json object went wrong.")
self.stopAnimating()
}
}, failure: { (operation, error) -> Void in
self.handleError(error as NSError)
self.stopAnimating()
})
}
and have it down to:
func createAccount() {
let request = HttpRequest()
let response = request.post(params: setParams(), URLExtension: "account")
if (response != nil) {
successfulRequest(response: response!)
} else {
failedRequest(response: response!)
}
}
func successfulRequest(response: AnyObject) {
self.stripeAccountID = response["id"] as! String
createMainBillSplitter()
self.stopAnimating()
performSegue(withIdentifier: "segueToFinalRegistrationViewController", sender: self)
}
func failedRequest(response: AnyObject) {
self.stopAnimating()
self.handleError(response["failed"] as! NSError)
}
where HTTPRequest is:
class HttpRequest {
let manager = AFHTTPSessionManager()
let baseURL = "https://splitterstripeservertest.herokuapp.com/account"
func post(params: [String: Any], URLExtension: String) -> AnyObject? {
let URL = baseURL + URLExtension
var response = [String: Any]()
manager.requestSerializer = AFHTTPRequestSerializer()
manager.responseSerializer = AFHTTPResponseSerializer()
manager.post(URL, parameters: params, progress: nil, success: {(_ task: URLSessionDataTask, _ responseObject: Any) -> Void in
do {
response = try JSONSerialization.jsonObject(with: responseObject as! Data, options: .mutableContainers) as! [String: Any]
} catch {
print("Serialising new account json object went wrong.")
}
}, failure: { (operation, error) -> Void in
response = ["failed": error]
})
return response as AnyObject?
}
func handleError(_ error: NSError) -> UIAlertController {
let alert = UIAlertController(title: "Please Try Again", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
return alert
}
}
But, I'm getting errors because the response is nil, which I'm sure is because there aren't completion handlers. I just don't understand enough how to implement them in this situation, so would really appreciate a push in the right direction. Thanks in advance!
You are getting confused with sync vs async operations.
The manager.post function will create your http request and will call the success closure when it is done. But since that function is implemented as an async operation, your code will not stop while that http request is being executed. So, your code will continue to be executed, and in your case, the very next line is you returning the response that is basically your empty array of Strings.
func post(params: [String: Any], URLExtension: String) -> AnyObject? {
let URL = baseURL + URLExtension
var response = [String: Any]()
manager.requestSerializer = AFHTTPRequestSerializer()
manager.responseSerializer = AFHTTPResponseSerializer()
manager.post(URL, parameters: params, progress: nil, success: {(_ task: URLSessionDataTask, _ responseObject: Any) -> Void in
// this closure is executed only when the request is completed
do {
response = try JSONSerialization.jsonObject(with: responseObject as! Data, options: .mutableContainers) as! [String: Any]
} catch {
print("Serialising new account json object went wrong.")
}
}, failure: { (operation, error) -> Void in
response = ["failed": error]
})
return response as AnyObject? // <<-- this line is executed right after the manager.post line above, but the success closure was not called yet because the request is still going on.
}
So, what you need to do is to not return the response right after the manager.post was called, but return it from inside the success closure. But you cannot simply use a return response statement. You need to pass the response as a parameter to a callback closure that you would pass to your request.post function.
Something like this:
func createAccount() {
let request = HttpRequest()
let response = request.post(params: setParams(),
URLExtension: "account",
success: {response in
// enter here the code to be executed when request is completed.
successfulRequest(response: response)
},
fail: {response in
failedRequest(response: response)
},)
}
and your class HttpRequest post function would be:
func post(params: [String: Any], URLExtension: String, success:([String: Any] -> Void), fail:([String: Any] -> Void)) -> AnyObject? {
let URL = baseURL + URLExtension
manager.requestSerializer = AFHTTPRequestSerializer()
manager.responseSerializer = AFHTTPResponseSerializer()
manager.post(URL, parameters: params, progress: nil, success: {(_ task: URLSessionDataTask, _ responseObject: Any) -> Void in
do {
response = try JSONSerialization.jsonObject(with: responseObject as! Data, options: .mutableContainers) as! [String: Any]
success(response)
} catch {
print("Serialising new account json object went wrong.")
}
}, failure: { (operation, error) -> Void in
response = ["failed": error]
fail(response)
})
}
PS: your code is assuming that it will always be able to decode the JSON response. Although you are using do / catch, if for some reason the JSON decoding fails, no response is being send back to your calling function. So, the app will just be stuck. I suggest you calling the fail() callback inside your catch block.
Related
enter image description hereI'm new to Swift and I just faced a problem . any help and suggestion is welcome, also i've seen Alomofire, but I was not able to setup Alamofire because of some errors, and Also I need help in Alamofire :|
request.addValue("application/json", forHTTPHeaderField:
"Accept")
request.addValue("application/x-www-form-urlencoded",
forHTTPHeaderField: "Content-Type")
let postString = ["grant_type" : "password" ,
"username" : EntPhoneNumber.text! ,
"password" : EntPassword.text! ,] as [String :
Any]
do {
request.httpBody = try JSONSerialization.data(withJSONObject:
postString)
}catch let error {
print(error.localizedDescription)
DisplayMessage(UserMessage: "Something went wrong , please try
again!")
return
}
let task = URLSession.shared.dataTask(with: request)
{
(data : Data? , response : URLResponse? , error : Error?) in
self.removeActivtyIndicator(activityIndicator:
MyActivityIndicator)
if error != nil
{
self.DisplayMessage(UserMessage: "1Could not successfully
perform this request , please try again later.")
print("error = \(String(describing : error))")
return
}
// let's convert response sent from a server side code to a
NSDictionary object:
do { let json = try JSONSerialization.jsonObject(with: data!,
options: .mutableContainers) as? NSDictionary
print(json!)
if let parseJSON = json
{
let userID = parseJSON["grant_type"] as? String
print("access_token : \(String(describing: userID))");
if userID == nil
{
//display an alert dialog with a friendly error
message
self.DisplayMessage(UserMessage: "2Could not
successfully perform this request , please try later.")
return
}
else
{
self.DisplayMessage(UserMessage: "3successfully
loged in.")
}
}
...
as you can see this is my code which i'm posting a request to api to get an access_token but i receive an error :
{error = "invalid_request";
"error_description" = "The mandatory 'grant_type' parameter is missing.";
}
These are the parameters that I should post to API and in postman it works properly but my code does not work at all in compiler.
you are making it too complex
let postString = ["grant_type" : "password" ,
"username" : EntPhoneNumber.text! ,
"password" : EntPassword.text! ,] as [String :
Any]
Alamofire.request(URL, method: .post, parameters: postString, encoding: URLEncoding.methodDependent, headers: nil).responseJSON { response in
if let json = response.result.value {
print("Response: ",json)
}
}
try this.
This is all you need. If you have any doubt ask here.
Follow this link to install Alamofire using CocoaPods
https://www.raywenderlich.com/156971/cocoapods-tutorial-swift-getting-startedenter link description here
I have created this custom method for API request.
func request(_ method: HTTPMethod
, _ URLString: String
, parameters: [String : AnyObject]? = [:]
, headers: [String : String]? = [:]
, completion:#escaping (Any?) -> Void
, failure: #escaping (Error?) -> Void) {
Alamofire.request(URLString, method: method, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
.responseJSON { response in
switch response.result {
case .success:
completion(response.result.value!)
case .failure(let error):
failure(error)
}
}
}
You need to encode the login and password parameters in query while sending the request. Lets create a separate Network class which has loginRequest(username:password) function which makes the API request to the server with username and password params. The login method has a closure which either returns a userId string or Error.
class Networking {
enum NetworkError: Error {
case parsingData
case jsonParsing
case emptyData
case apiError(error: Error)
}
func parseJSON(from data: Data) throws -> Any {
do {
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
return json
} catch {
return error
}
}
func loginRequest(username: String, password: String, completion: #escaping (String?, NetworkError?) -> ()) {
var request = URLRequest(url: URL(string: "api.nahadeh.com/connect/token")!)
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let queryParams = "grant_type=password&username=\(username)&password=\(password)"
request.httpBody = queryParams.data(using: .utf8, allowLossyConversion: false)
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error != nil else {
completion(nil, NetworkError.apiError(error: error!))
return
}
guard let data = data else {
return completion(nil, NetworkError.emptyData)
}
do {
let json = try self.parseJSON(from: data) as! [String: Any]
guard let accessToken = json["access_token"] as? String else {
completion(nil, NetworkError.jsonParsing)
return
}
completion(accessToken, nil)
} catch {
completion(nil, NetworkError.jsonParsing)
}
}.resume()
}
}
Now from your UIViewController subclass you can create the instance of Networking class and call the login method with username and password params
class LoginViewController: UIViewController {
let networking = Networking()
func login(username: String, password: String) {
networking.loginRequest(username: username, password: password) { (token, error) in
if let token = token {
print(token)
self.displayMessage("Successfully loged in. Go to home screen")
return
}
if let nError = error {
switch nError {
case .jsonParsing:
self.displayMessage("jsonParsingError")
case .emptyData:
self.displayMessage("emptyData")
case .apiError(let error):
print(error.localizedDescription)
self.displayMessage("Could not successfully perform this request , please try later.")
case .parsingData:
self.displayMessage("parsingData")
}
}
}
}
func displayMessage(_ message: String, completion: (() -> Void)? = nil) {
let alert = UIAlertController(title: "Message", message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert, animated: true, completion: completion)
}
}
Using Alamofire it's very easy to make API request, just pass the params and it will do the work for you
func loginRequest(username: String, password: String, completion: #escaping (String?, Error?) -> ()) {
let params: [String: String] = [
"grant_id": "password",
"username": username,
"password" : password
]
Alamofire.request("api.nahadeh.com/connect/token", method: .post, parameters: params, encoding: .httpBody, headers: nil).validate().response { (response) in
switch response.result {
case .success(let value):
let accessToken = value["access_token"]
completion(accessToken, nil)
case .failure(let error):
completion(nil, error)
}
}
}
I'm trying to read the posts from a Facebook page using the following code:
class FacebookGraphAPI {
class func getPosts(fromPageWithID pageId: String, parameters: [String: AnyObject]?, completion: #escaping ([FacebookPost]?, Error?) -> Void) {
self.getAccessToken { (accessToken, error) in
if error != nil {
completion(nil, error)
return
}
let params = ["access_token": accessToken, "fields": "created_time,message,story"]
if let request = FBSDKGraphRequest(graphPath: "\(pageId)/posts", parameters: params) {
request.start { (connection, result, error) in
if error != nil {
completion(nil, error)
return
}
guard let resultDict = result as? [String: AnyObject],
let data = resultDict["data"] as? NSArray
else {
completion(nil, nil)
return
}
var posts = [FacebookPost]()
for item in data {
posts.append(FacebookPost(dict: item as! NSDictionary))
}
completion(posts, nil)
}
}
completion(nil, nil)
}
}
class func getAccessToken(completion: #escaping (String?, Error?) -> Void) {
let clientId = Bundle.main.object(forInfoDictionaryKey: "FacebookAppID") as! String
let clientSecret = Bundle.main.object(forInfoDictionaryKey: "FacebookAppSecret") as! String
let params = ["client_id": clientId, "client_secret": clientSecret, "grant_type": "client_credentials", "fields": "access_token"]
if let request = FBSDKGraphRequest(graphPath: "oauth/access_token", parameters: params) {
request.start(completionHandler: { (connection, result, error) in
if error != nil {
completion(nil, error)
return
}
guard let resultDict = result as? [String: AnyObject] else {
completion(nil, nil)
return
}
let accessToken = resultDict["access_token"] as! String
completion(accessToken, nil)
})
}
}
}
Which I then call using e.g. the following:
FacebookGraphAPI.getPosts(fromPageWithID: "{page-id}", parameters: ["limit": 5 as AnyObject]) { (posts, error) in
guard error == nil else { return }
...
}
The error I'm getting is: -[_SwiftValue length]: unrecognized selector sent to instance on the second FBSDKGraphRequest start.
I tried removing the first FBSDKGraphRequest and then I at least get a response in the completionHandler. It almost seams as if I can't make more than one FBSDKGraphRequest.
Any help is greatly appreciated.
Thanks in advance!
I finally managed to find the issue, when thinking about the error and remembering that the FBSDKCoreKit framework was written in Objective-C.
All I needed to do was cast accessToken inside the parameters array to an NSString.
I changed the following:
let params = ["access_token": accessToken, "fields": "created_time,message,story"]
To:
let params = ["access_token": accessToken! as NSString, "fields": "created_time,message,story"]
I am trying to parse data using AFNetworking & swift 3.0 and xcode 8.0 but i am getting error like below.below code works fine for swift 2.3 but not working in 3.0
Or if is there anyone know about AFNetworking & swift 3.0 using xcode 8.0 for POST & GET request please tell me. with simple example.
Thanks in Advance
You can see below error.
func callApi(apiName: String, param: [String : AnyObject]?, data: NSDictionary?, withMethod type: String, CompletionHandler:#escaping (_ code: Int, _ error:NSError?, _ response:AnyObject?) -> Void)
{
MBProgressHUD.showAdded(to: AppDelegateObj.window, animated: true)
let str_URL : String = kHOSTPATH+apiName
let manager: AFHTTPSessionManager = AFHTTPSessionManager()
if (type == kREQ_POST) {
manager.POST(str_URL, parameters: param, constructingBodyWithBlock: { (formData: AFMultipartFormData!) in
if data?.allValues.count != 0 && data != nil
{
let fileUrl = NSURL(fileURLWithPath: (data?.valueForKey("filePath"))! as! String)
try! formData.appendPartWithFileURL(fileUrl, name: (data?.valueForKey("key"))! as! String)
}
}, progress: { (NSProgress) in
}, success: { (task:URLSessionDataTask, responseObject) -> Void in
CompletionHandler(code: 1, error: nil, response:responseObject)
MBProgressHUD.hideHUDForView(AppDelegateObj.window, animated: true)
}, failure: { (task:URLSessionDataTask?, error:NSError) -> Void in
CompletionHandler(code: 0, error: error, response:nil)
MBProgressHUD.hideHUDForView(AppDelegateObj.window, animated: true)
})
}
else {
manager.GET(str_URL, parameters: param, progress: { (NSProgress) in
}, success: { (task:URLSessionDataTask, responseObject) -> Void in
CompletionHandler(code: 1, error: nil, response:responseObject)
MBProgressHUD.hideHUDForView(AppDelegateObj.window, animated: true)
}, failure: { (task:URLSessionDataTask?, error:NSError) -> Void in
CompletionHandler(code: 0, error: error, response:nil)
MBProgressHUD.hideHUDForView(AppDelegateObj.window, animated: true)
})
}
}
but i am getting error like this
cannot convert the value of type (URLSessionDataTask?,NSError)->Void to expected argument type '((URLSessionDataTask?,NSError)->Void)?'
Change your error type from NSERROR to ERROR. Type compatibility changed from Swift2.3 to Swift 3.0
Try this:
func Get()
{
let url = NSURL(string: Webserive.UserProfileInfoList + "/" + String(300))
let request = NSURLRequest(url: url! as URL)
NSURLConnection.sendAsynchronousRequest(request as URLRequest, queue: OperationQueue.main) {(response, data, error) in
self.GetData(data: data! as NSData)
}
}
func GetData(data:NSData){
let dict: NSDictionary!=(try! JSONSerialization.jsonObject(with: data as Data, options: JSONSerialization.ReadingOptions.mutableContainers)) as! NSDictionary
print(dict)
self.PostData()
}
func PostData(){
let parameters : NSMutableDictionary? = [
"UserID": String(300),
"UserProfileID": String(356)]
let manager = AFHTTPSessionManager()
let serializerRequest = AFJSONRequestSerializer()
serializerRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
manager.requestSerializer = serializerRequest
let serializerResponse = AFJSONResponseSerializer()
serializerResponse.readingOptions = JSONSerialization.ReadingOptions.allowFragments
serializerResponse.acceptableContentTypes = ((((NSSet(object: "application/json") as! Set<String>) as Set<String>) as Set<String>) as Set<String>) as Set<String>;
manager.responseSerializer = serializerResponse
manager.post(Webserive.DefaultProfile, parameters: parameters, progress: nil, success: { (task: URLSessionDataTask, responseObject: Any?) in
if (responseObject as? [String: AnyObject]) != nil {
print("responseObject \(responseObject)")
}
}) { (task: URLSessionDataTask?, error: Error) in
print("POST fails with error \(error)")
}
}
I had similar issue. Do not know what is the root of the problem but you can help yourself by not specifying some parameter's type, in this case 'error' in POST method call:
manager.post("http://...", parameters: [:], progress: nil, success: { (operation: URLSessionDataTask, responseObject: Any?) in
print("Success")
}, failure: { (operation: URLSessionDataTask?, error) in
let error = error as? Error
print("Failure, error is \(error?.description)")
})
With this code pattern I can build the code with Xcode 8.3 and Swift 3.
EDIT:
If you have some problem with "Cannot convert value of type...", please check type that function expects.
My problem was that I had declaration of Error as CoreData one and AFNetworking certainly does not return that Error type.
class Error: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
}
What I needed to change was:
(operation: URLSessionDataTask?, error: Error?)
to:
(operation: URLSessionDataTask?, error: Swift.Error?)
Is it possible to have a Alamofire validator that gets the parsed JSON response, check a property and return true / false depending on that value?
I have an API that always returns 200 response codes, but the response has a success property.
I would like to check this property before the responseJSON callback is fired and only call responseJSON if success == true.
Is this possible with custom validators?
Found a solution I feel ok with. First I created extension methods that check for errors and extract the data I'm interested in. I have one success callback and one error callback.
import Foundation
import Alamofire
extension Request {
public func apiSuccess(
queue queue: dispatch_queue_t? = nil,
options: NSJSONReadingOptions = .AllowFragments,
completionHandler: [String:AnyObject] -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: Request.JSONResponseSerializer(options: options),
completionHandler: { response in
if let jsonValue = response.result.value as? [String:AnyObject] {
let success = jsonValue["success"] as! Bool
if (success) {
completionHandler(jsonValue["object"] as! [String:AnyObject])
}
}
}
)
}
public func apiError(
queue queue: dispatch_queue_t? = nil,
options: NSJSONReadingOptions = .AllowFragments,
completionHandler: [String] -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: Request.JSONResponseSerializer(options: options),
completionHandler: { response in
if let jsonValue = response.result.value as? [String:AnyObject] {
let success = jsonValue["success"] as! Bool
if (!success) {
let errorDict = jsonValue["errors"] as! [String:[String]]
var errors : [String] = []
errorDict.keys.forEach { key in
errors += errorDict[key] as [String]!
}
completionHandler(errors)
}
}
}
)
}
}
Then I can use it like this:
Alamofire.request(.POST, url,
parameters: parameters,
encoding: .JSON)
.apiSuccess { response in
print("Success Callback", response)
}
.apiError { errors in
print("Errors ", errors)
}
I don't think it is. Validator blocks don't receive the response data as arguments, only headers and such.
I am parsing the data and I am getting the responseString also.But my problem is I want to convert it into dictionary and then I want to get the values from that jsonObject.
But I am unable to get that.
My Code is as follows
func loginRequest(url:String, withParams params: [String: String?], postCompleted : (succeeded: Bool, msg: String) -> ()){
let request = NSMutableURLRequest(URL: NSURL(string: url)!)
let session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
var err: NSError?
var bodyData = ""
for (key,value) in params{
if (value == nil){ continue }
let scapedKey = key.stringByAddingPercentEncodingWithAllowedCharacters(
.URLHostAllowedCharacterSet())!
let scapedValue = value!.stringByAddingPercentEncodingWithAllowedCharacters(
.URLHostAllowedCharacterSet())!
bodyData += "\(scapedKey)=\(scapedValue)&"
}
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
let task = session.dataTaskWithRequest(request,
completionHandler: {data, response, error -> Void in
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
postCompleted(succeeded: true, msg: dataString! as String)
print(dataString!)
if let datas = dataString!.dataUsingEncoding(NSUTF8StringEncoding) {
do{
let json = try NSJSONSerialization.JSONObjectWithData(datas, options: []) as? [String:AnyObject]
//return json
print(json)
}
catch
{
print("Something went wrong")
}
}
})
task.resume()
}
I am calling that method like this
self.loginRequest("http://stream.gsr-india.com:8080/pgn_service/REST/WebService/GetUserDetails",
withParams: ["first_name":firstNameTextField.text,"last_name":lastNameTextField.text , "application_id":uniqueIdTextField.text])
{
(succeeded: Bool, msg: String) -> () in
if(succeeded) {
if msg == "0"
{
//Incorrect data...
}
else
{
//The login it's ok...
}
}
}
Can anyone Please help me to resolve that.
Thanks In Advance.
Change method declaration to
func loginRequest(url:String, withParams params: [String: String?], postCompleted : (succeeded: Bool, msg: NSDictionary?) -> ()){
then change the task handler to
let task = session.dataTaskWithRequest(request,
completionHandler: {data, response, error -> Void in
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(dataString!)
do{
let json = try NSJSONSerialization.JSONObjectWithData(datas, options: []) as? [String:AnyObject]
postCompleted(succeeded: true, msg: json)
print(json)
}
catch {
print("Something went wrong")
}
}
})
change the callback to
(succeeded: Bool, msgDict: NSDictionary?) -> () in
if let dict = msgDict {
// call any value like dict["error"] etc
}