I am trying to create function which return Observable<(HTTPURLResponse, Any)> using RxAlamofire:
func getResponse(credentialData: Credentials, ulr: String)->Observable<(HTTPURLResponse, Any)>{
let credentialData = "\(credentialData.username):\(credentialData.password)".data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
let base64Credentials = credentialData.base64EncodedString()
let headers = ("Authorization", "Basic \(base64Credentials)")
let header = HTTPHeaders.init(dictionaryLiteral: headers)
return Observable.create{ observer in
requestJSON(.get, ulr, parameters: nil, encoding: JSONEncoding.default, headers: header)
.subscribe(onNext: { response in
observer.onNext(response)
} ,
onError: { error in
observer.onError(error)
})
return Disposables.create()
}
}
}
but I get this below warning:-
Result of call to 'subscribe(onNext:onError:onCompleted:onDisposed:)'
is unused
How to fix it? After adding .disposed(by: disposeBag) my function isn't working.
Hello you have to add your request into a DisposeBag :
return Observable.create { observer in
requestJSON(.get, ulr, parameters: nil, encoding: JSONEncoding.default, headers: header)
.subscribe(onNext: { response in
observer.onNext(response)
}, onError: { error in
observer.onError(error)
}).disposed(by: disposeBag)
return Disposables.create()
}
That should works.
Related
I'm trying to create a function which returns Observable<(HTTPURLResponse, Any)> using RxAlamofire:
class APIManager {
let disposeBag = DisposeBag()
func getResponse(credentialData: Credentials, ulr: String)->Observable<(HTTPURLResponse, Any)>{
let credentialData = "\(credentialData.username):\(credentialData.password)".data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
let base64Credentials = credentialData.base64EncodedString()
let headers = ("Authorization", "Basic \(base64Credentials)")
let header = HTTPHeaders.init(dictionaryLiteral: headers)
return Observable.create{ observer in
requestJSON(.get, ulr, parameters: nil, encoding: JSONEncoding.default, headers: header)
.subscribe(onNext: { response in
observer.onNext(response)
} ,
onError: { error in
observer.onError(error)
},
onCompleted: {},
onDisposed: {}).disposed(by: self.disposeBag)
return Disposables.create()
}
}
}
The code into onNext is not executed; it's going through onDisposed only.
How can I fix it?
Why are you wrapping requestJSON function with another Observable which is just forwarding the same element and error, when that function is already returning the Observable you need?.
Isn't this enough for you?
class APIManager {
func getResponse(credentialData: Credentials, ulr: String) -> Observable<(HTTPURLResponse, Any)> {
let credentialData = "\(credentialData.username):\(credentialData.password)"
.data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
let base64Credentials = credentialData.base64EncodedString()
let headers = ("Authorization", "Basic \(base64Credentials)")
let header = HTTPHeaders.init(dictionaryLiteral: headers)
return requestJSON(.get, ulr, parameters: nil, encoding: JSONEncoding.default, headers: header)
}
}
In RxJS you can use the value from a observable for a new observable. For example:
this.authService.login(username, password).pipe(
switchMap((success: boolean) => {
if(success) {
return this.contactService.getLoggedInContact()
} else {
return of(null)
}
})
).subscribe(contact => {
this.contact = contact
})
But now I have to do a project in Swift and I want to achieve the same thing. I can get the two methods working, but using the result of the first observable for the second observable is something i can't get working. The switchMap pipe is something that does not exist in RxSwift and I cannot find the equivalent.
I've tried mapping the result of the login function to the observable and then flatmapping it, but unfortunately that didn't work.
What is the best way to do this in Swift without using a subscribe in a subscribe?
EDIT I've tried flat map:
APIService.login(email: "username", password: "password")
.flatMapLatest { result -> Observable<Contact> in
if result {
return APIService.getLoggedInContact()
} else {
return .of()
}
}.subscribe(onNext: {result in
print("Logged in contact: \(result)")
}, onError: {Error in
print(Error)
}).disposed(by: disposeBag)
But unfortunately that didn't work, I get an error Thread 1: EXC_BAD_ACCESS (code=1, address=0x13eff328c)
EDIT2:
This is the login function
static func login(email: String, password: String) -> Observable<Bool> {
return Observable<String>.create { (observer) -> Disposable in
Alamofire.request(self.APIBASEURL + "/contact/login", method: .post, parameters: [
"email": email,
"password": password
], encoding: JSONEncoding.default).validate().responseJSON(completionHandler: {response in
if (response.result.isSuccess) {
guard let jsonData = response.data else {
return observer.onError(CustomError.api)
}
let decoder = JSONDecoder()
let apiResult = try? decoder.decode(ApiLogin.self, from: jsonData)
return observer.onNext(apiResult!.jwt)
} else {
return self.returnError(response: response, observer: observer)
}
})
return Disposables.create()
}.map{token in
return KeychainWrapper.standard.set(token, forKey: "authToken")
}
}
This is the getLoggedInContact function
static func getLoggedInContact() -> Observable<Contact> {
return Observable.create { observer -> Disposable in
Alamofire.request(self.APIBASEURL + "/contact/me", method: .get, headers: self.getAuthHeader())
.validate().responseJSON(completionHandler: {response in
if (response.result.isSuccess) {
guard let jsonData = response.data else {
return observer.onError(CustomError.api)
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(.apiNewsDateResult)
let apiResult = try? decoder.decode(Contact.self, from: jsonData)
return observer.onNext(apiResult!)
} else {
return self.returnError(response: response, observer: observer)
}
})
return Disposables.create()
}
}
There is operator flatMapLatest which does exactly the same as switchMap in RxJS.
You can find usage example here
How can I write a generic wrapper for alamofire request ?
How can I convert the POST and GET function to Generic function in the following code?
I need to have generic request functions that show different behaviors depending on the type of data received.
Also, Can the response be generic?
My non-generic code is fully outlined below
import Foundation
import Alamofire
import SwiftyJSON
// for passing string body
extension String: ParameterEncoding {
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var request = try urlRequest.asURLRequest()
request.httpBody = data(using: .utf8, allowLossyConversion: false)
return request
}
}
public class ConnectionManager {
func Post (FirstName: String, LastName: String, NationalID: String, NationalCode: String, BirthDate: Date,Image: String,isFemale: Bool,Age:Int64,Avg:Double, completion: #escaping CompletionHandler) {
let body: [String:Any] = [
"FirstName":FirstName,
"LastName": LastName,
"NationalID": NationalID,
"NationalCode": NationalCode,
"BirthDate":BirthDate,
"Image": Image,
"isFemale": isFemale,
"Age": Age,
"Avg": Avg
]
Alamofire.request(BASE_URL, method: .post, parameters: body, encoding: JSONEncoding.default, headers: HEADER).responseJSON { (response) in
if response.response?.statusCode == 200 {
guard let data = response.result.value else { return }
print(data)
completion(true)
} else {
print("error reg auth service")
guard let er = response.result.value else { return }
print(er)
completion(false)
debugPrint(response.result.error as Any)
}
}
}
func get(FirstName: String, LastName: String, NationalID: String, NationalCode: String, BirthDate: Date,Image: String,isFemale: Bool,Age:Int64,Avg:Double, completion: #escaping CompletionHandler) -> [Person] {
let body: [String:Any] = [
"FirstName":FirstName,
"LastName": LastName,
"NationalID": NationalID,
"NationalCode": NationalCode,
"BirthDate":BirthDate,
"Image": Image,
"isFemale": isFemale,
"Age": Age,
"Avg": Avg
]
Alamofire.request(BASE_URL, method: .get, parameters: body, encoding: JSONEncoding.default, headers: HEADER).responseJSON { (response) in
if response.response?.statusCode == 200 {
print("no error login in authservice")
guard let data = response.result.value else { return }
print(data)
completion(true)
}
else if response.response?.statusCode == 400 {
print("error 400 login in authservice")
guard let er = response.result.value else { return }
print(er)
debugPrint(response.result.error as Any)
completion(false)
} else {
print("error ## login in authservice")
guard let er = response.result.value else { return }
print(er)
debugPrint(response.result.error as Any)
completion(false)
}
}
return [Person]()
}
}
The best idea is to use the URLRequestConvertible Alamofires protocol and create your own protocol and simple structs for every API request:
import Foundation
import Alamofire
protocol APIRouteable: URLRequestConvertible {
var baseURL: String { get }
var path: String { get }
var method: HTTPMethod { get }
var parameters: Parameters? { get }
var encoding: ParameterEncoding { get }
}
extension APIRouteable {
var baseURL: String { return URLs.baseURL }
// MARK: - URLRequestConvertible
func asURLRequest() throws -> URLRequest {
let url = try baseURL.asURL().appendingPathComponent(path)
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = method.rawValue
return try encoding.encode(urlRequest, with: parameters)
}
}
and request can look like this:
struct GetBooks: APIRouteable {
var path: String
var method: HTTPMethod
var parameters: Parameters?
var encoding: ParameterEncoding
}
and inside the APIClient prepare the generic method:
func perform<T: Decodable>(_ apiRoute: APIRouteable,
completion: #escaping (Result<T>) -> Void) {
let dataRequest = session
.request(apiRoute)
dataRequest
.validate(statusCode: 200..<300)
.responseDecodable(completionHandler: { [weak dataRequest] (response: DataResponse<T>) in
dataRequest.map { debugPrint($0) }
let responseData = response.data ?? Data()
let string = String(data: responseData, encoding: .utf8)
print("Repsonse string: \(string ?? "")")
switch response.result {
case .success(let response):
completion(.success(response))
case .failure(let error):
completion(.failure(error))
}
})
}
and use it:
func getBooks(completion: #escaping (Result<[Book]>) -> Void) {
let getBooksRoute = APIRoute.GetBooks()
perform(getBooksRoute, completion: completion)
}
I am trying to get data with flickr API and I wrote a simple code with Alamofire and swiftyJSON to get this data from flickr but I am able to print the data size but when I try to print the json, my catch block runs. my codes are shown below
func getPhotos(completion: #escaping CompletionHandler) -> Void {
let parameter: [String: Any] = [
"method": PHOTOS_METHOD,
"api_key": FLICKR_API_KEY,
"per_page": PER_PAGE,
"page": PAGE,
"format": FORMAT_TYPE,
"nojsoncallback": JSON_CALLBACK]
Alamofire.request(FLICKR_URL, method: .get, parameters: parameter, encoding: JSONEncoding.default, headers: HEADER).responseString { (response) in
if response.result.error == nil {
guard let data = response.data else {return}
do {
if let json = try JSON(data: data).array {
print(json)
}
completion(true)
} catch {
print("eroorrrre")
completion(false)
}
print("CALL CORRECT")
print(data)
completion(true)
}
else {
completion(false)
debugPrint(response.result.error as Any)
}
}
}
my console log
eroorrrre
CALL CORRECT
128 bytes
I am not sure what I am doing wrong here, any help would be appriciated
Try this
Alamofire.request(FLICKR_URL, method: .get, parameters: parameter, headers: HEADER).responseJSON { response in // call responseJSON instead of responseString
if response.result.isSuccess { // If http request is success
let json: JSON = JSON(response.result.value!) // JSON format from SwiftyJSON (I suppose you are using it)
guard let data = json.array else { // You suppose that json is array of objects
print("Unexpected JSON format")
completion(false)
}
print(data)
completion(true)
} else {
print(response.error)
completion(false)
}
}
I'm making a POST Request to my API. All of a sudden the request is being skipped. I have tried to debug into it, but until now without success.
This is my request:
#IBAction func checkLogin(_ sender: Any) {
guard let managedContext = self.managedObjectContext else { return }
let user = NSEntityDescription.insertNewObject(forEntityName: User.identifier, into: managedContext) as! User
let url = ""
let parameters: Parameters =
["username" : usernameTextField.text!, "password" : passwordTextField.text!]
Alamofire.request(url, method: .post, parameters: parameters, encoding: URLEncoding.default).responseJSON { (responseData) -> Void in
let results = JSON(responseData.result.value!)
print(results)
user.firstName = results["firstname"].string!
let responseString : String = responseData.response?.allHeaderFields["Set-Cookie"] as! String
if let range = responseString.range(of: ";"){
let startIndex = (responseString.range(of: "="))
let cookie = responseString[(startIndex?.upperBound)!...range.lowerBound]
user.setValue(cookie, forKey: "token")
}
} do {
try self.dataController.saveContext()
}catch {
print("Save Error User")
}
I'm Using Alamofire 4.5 with Swift 3.1.
Please use different types of data request handling block and check again.
Alamofire.request(url, method: .post, parameters: parameters, encoding: URLEncoding.default)
.responseJSON { response in
print("JSON Response")
}
.responseData { response in
print("Data Response")
}
.responseString { response in
print("String Response")
}
.responsePropertyList { response in
print("PropertyList Response")
}