RxAlamofire - how to get the response on error? - rxalamofire

I need the response body when an error occurs in an RxAlamofire call. I've seen this hack but I wonder if there's a cleaner way.
Inspired by it, I created this RxAlamofire fork with a similar hack. With it, errors will usually be an instance of DataResponseError so you can do this:
RxAlamofire.data(method, url).subscribe(
onError: { error in
if let error = error as? DataResponseError<Data> {
// Get response body (in this case, convert it to a String)
if let data = error.response.data {
let message = String(data: data, encoding: String.Encoding.utf8)
print("Message: \(message)")
}
// Get status code
if let statusCode = error.response.response?.statusCode {
print("Status code: \(statusCode)")
}
}
}
)

Issue description. I'm using RxAlamofire to make network requests, and I needed to get information from the body of the error response.
I've made a hack in a folloing way:
Added a PError:
import UIKit
import Alamofire
import ObjectMapper
class PError: Error, Mappable {
var message: String?
var statusCode: Int?
required init?(map: Map) {
}
func mapping( map: Map) {
message <- map["error.message"]
statusCode <- map["error.statusCode"]
}
}
And now added such extensions to DataRequest:
import Alamofire
import ObjectMapper
extension DataRequest {
//Overriding several methods from Alamofire Validation
#discardableResult
public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
return validate { [unowned self] _, response, bodyData in
return self.validate(statusCode: acceptableStatusCodes, response: response, bodyData: bodyData)
}
}
//Overriding several methods from Alamofire Validataion
fileprivate func validate<S: Sequence>(
statusCode acceptableStatusCodes: S,
response: HTTPURLResponse, bodyData: Data?)
-> ValidationResult
where S.Iterator.Element == Int
{
if acceptableStatusCodes.contains(response.statusCode) {
return .success
} else {
var error: Error = AFError.responseValidationFailed(reason: AFError.ResponseValidationFailureReason.unacceptableStatusCode(code: response.statusCode))
if let bodyData = bodyData {
if let jsonString = String(data: bodyData, encoding: .utf8) {
if let errorNew = Mapper<PError>().map(JSONString: jsonString)
{
error = errorNew
}
}
}
return .failure(error)
}
}
}
Next, somewhere in the code you'll be able to work with this custom error object:
if let err = error as? PError {
status = err.message ?? "No message in the error description"
}
else

Related

how to fetch data from API and set to the Image

This is my Model And I want to fetch data of publisherBanner and set
to the View But I can not set the image in view
import Foundation
public struct Banner: Decodable {
public let publisherBanners: [PublisherBanner]
public init(publisherBanners: [PublisherBanner]) {
self.publisherBanners = publisherBanners
}
}
public struct PublisherBanner: Decodable, Hashable {
public var id = UUID()
// public let bannerFor: String
// public let imageName: String
public let url: String
public init(url: String) {
self.url = url
}
}
This is my ViewModel
class BannerVM: ObservableObject {
#Published var datas = [PublisherBanner]()
let url = "apiUrlExample"
init() {
getData(url: url)
}
func getData(url: String) {
guard let url = URL(string: "\(url)") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
if let data = data {
do {
let results = try JSONDecoder().decode(Banner.self, from: data)
DispatchQueue.main.async {
self.datas = results.publisherBanners
}
}
catch {
print(error)
}
}
}.resume()
}
}
And this is My View where I want to set Image
struct BannerView: View {
#StateObject var bannerObject = BannerVM()
var body: some View{
ScrollView(.horizontal,showsIndicators: false){
HStack(spacing:15) {
ForEach(bannerObject.datas, id: \.id){ item in
AsyncImage(url: URL(string: "\(item.url)")) { image in
image
.resizable().padding(4)
.frame(width: 150, height: 215)
} placeholder: {
Image("logo_gray").resizable().padding(1)
.frame(width: 150, height: 215)
}
}
}
}
.padding(8)
}
}
please help me for fetch the Image of My API
I am trying to fetch but i failed many times and please help me. And
thank you in advance.
Please read the error message you get
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "publisherBanners", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "id", intValue: nil)], debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
It says that the value for key id in PublisherBanner is an Int, you have to declare
public struct PublisherBanner: Decodable, Hashable, Identifiable {
public let id: Int
public let url: URL
}
By the way you can decode the url directly to URL and the init method is for free.
And as PublisherBanner already conforms to Identifiable the code to load the image can be shortened to
ForEach(bannerObject.datas) { item in
AsyncImage(url: item.url) { image in
Another by the way is that String Interpolation in URL(string: "\(url)") is redundant because url is already a String. This is sufficient: URL(string: url)

How to update nested structure of a published variable using another api call

I have a published var called spotWallet in my BinanceStore class which conforms to observaleObject. I can create a publisher and subscribe to it. but there is property in my top level struct called balances which is an array of Balance struct. What I want to achieve is would like to update a property of Balance called price which comes from another network call. I cannot get that to work, in clean combine swiftui way.
class BinanceStore: ObservableObject, Identifiable {
#Published var spotWallet: SpotWallet?
#Published var loadingError: String = ""
#Published var showAlert: Bool = false
private var cancellableSet: Set<AnyCancellable> = []
private let binanceFetcher: BinanceFetchable
init(binanceFetcher: BinanceFetchable) {
self.binanceFetcher = binanceFetcher
}
func AccountDetailsOnSpot() async {
binanceFetcher.getAccountDetailsOnSpot()
.sink { (dataResponse) in
if dataResponse.error != nil {
self.createAlert(with: dataResponse.error!)
} else {
self.spotWallet = dataResponse.value!
}
}.store(in: &cancellableSet)
}
func createAlert( with error: NetworkError ) {
loadingError = error.localizedDescription
self.showAlert = true
}
}
struct SpotWallet: Codable, Hashable {
let makerCommission, takerCommission, buyerCommission, sellerCommission: Int
let canTrade, canWithdraw, canDeposit: Bool
let updateTime: Int
let accountType: String
let balances: [Balance]
let permissions: [String]
}
protocol BinanceFetchable {
func getAccountDetailsOnSpot() -> AnyPublisher<DataResponse<SpotWallet, NetworkError>, Never>
}
class BinanceFetcher {
var coinPublisher = PassthroughSubject<CoinBalance, Never>()
}
extension BinanceFetcher: BinanceFetchable {
func getAccountDetailsOnSpot() -> AnyPublisher<DataResponse<SpotWallet, NetworkError>, Never> {
let endpoint = "/api/v3/account"
let params = BinanceWrapper.shared.makeRequestReady(queries: nil)
let url = URL(string: "\(BinanceWrapper.BINANCE_BASE_URL)\(endpoint)")!
return AF.request(url, parameters: params, encoding: URLEncoding.default, headers: BinanceWrapper.BINANCE_HTTP_HEADERS)
.validate()
.publishDecodable(type: SpotWallet.self)
.map { response in
response.mapError { error in
return NetworkError.parsing(description: error.localizedDescription)
}
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}

ErrorType protocol in weather app

I am very new to Swift and trying to create weather app. I have protocol func weatherManagerFailedToLoadCityWithError(error: ErrorType). In weatherManager.swift have some delegate
} else if status == 404 {
// City not found
if self.delegate != nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate?.weatherManagerFailerToLoadCityWithError(.InvalidResponse)
})
}
} else {
// Some other here?
if self.delegate != nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate?.weatherManagerFailerToLoadCityWithError(.MissingData)
})
}
}
What should i do i weatherController.swift in this code block
func weatherManagerFailedToLoadCityWithError(error: ErrorType) {
}
Any suggestion?
You can do it like this:
private struct ErrorInformation {
static let Domain = "us.firmaName"
static let ServerNotFoundDomain = "\(ErrorInformation.Domain).notFound"
}
private extension NSError {
static func serverNotFound() -> NSError {
let userInfo = [
NSLocalizedDescriptionKey: NSLocalizedString("Server Not Found", comment: ""),
NSLocalizedFailureReasonErrorKey: NSLocalizedString("The Server you are asking for is not available.", comment: ""),
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString("Please proof your URL bla-bla-bla.", comment: "")
]
return NSError(domain: ErrorInformation.ServerNotFoundDomain, code: 404, userInfo: userInfo)
}
And then call your function:
weatherManagerFailedToLoadCityWithError(error: NSError.serverNotFound())
If you want you can handle the Error in your function:
func weatherManagerFailedToLoadCityWithError(error: ErrorType) {
print("Error description: \(error.userInfo. NSLocalizedDescriptionKey)")
}
If you want more explanation, just post more of your code.

Alamofire 3.0.0-beta.3 Image Response Serialization

Can some one help me please, I'm trying to implement Alamofire image response serialization method : imageResponseSerializer,
Here is my code :
extension Alamofire.Request {
class func imageResponseSerializer() -> ResponseSerializer<UIImage, NSError> {
return ResponseSerializer<UIImage, NSError> { request, response, data, error in
guard let validData = data else {
let failureReason = "Data could not be serialized. Input data was nil."
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
if let image = UIImage(data: validData, scale: UIScreen.mainScreen().scale) {
return Result<UIImage, NSError>.Success(image)
}
else {
return .Failure(Error.errorWithCode(.JSONSerializationFailed, failureReason: "Unable to create image."))
}
}
}
func responseImage(completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<UIImage, NSError>) -> Void) -> Self {
return response(responseSerializer: Request.imageResponseSerializer(), completionHandler: { request, response, result in
completionHandler(request, response, result)
})
}
}
Error : cannot call value of non-function type 'NSHTTPURLResponse?'
I'm using : Xcode 7.0.1, Swift 2 and Alamofire 3.0.0-beta.3
Thank you,
You should really check out AlamofireImage. 1) It has all this implemented already. 2) It has many other awesome features that you will most likely find handy.

Appending JsonFile (UrL) in array to display on application

I am trying to add my data from my Json file into the application.I want append the Authors name from the Json file into the empty array.
I have added all of the areas that needed to be added when i run the simulation i get an empty array. I need it display the Authors first name on the simulator.
Does anyone know what i need to do to my code to make it work?
My Code :
var AuthorGlobal = [String]()
class ViewController: UIViewController, UITextFieldDelegate{
#IBOutlet weak var DisplayAuthor: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
DisplayAuthor.text="\(AuthorGlobal)"
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated);
Alamofire.request(.GET, "http://178.62.83.50/newsletters.json")
.responseJSON { response in
// print(response.request) // original URL request
// print(response.response) // URL response
// print(response.data) // server data
// print(response.result) // result of response serialization
if let _ = response.result.value {
// print("JSON: \(JSON)")
}
let json = JSON(data: response.data!)
if let Author = json["NewsLetter"][0]["Author"].string {
AuthorGlobal.append(Author)
}
if let LastName = json["NewsLetter"][0]["LastName"].string {
print(LastName)
}
if let ArticleTitle = json["NewsLetter"][0]["ArticleTitle"].string {
//Now you got your value
print(ArticleTitle)
}
if let Author = json["NewsLetter"][1]["Author"].string {
//Now you got your value
print(Author)
}
if let LastName = json["NewsLetter"][1]["LastName"].string {
//Now you got your value
print(LastName)
}
if let ArticleTitle = json["NewsLetter"][1]["ArticleTitle"].string {
//Now you got your value
print ("Article Title: " + (ArticleTitle))
}
}
}
I just tried by putting your json in my file system and loading it locally. Below is my code on Swift 2 and it all worked fine. You might want to check the JSON data coming correctly in your service call. Also, try to compare it line by line with my code to see if you missed out something. Its too late for me to call it a day so bear with me to not pointing out the exact root cause in your code!
var AuthorGlobal = [String]()
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var DisplayAuthor: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// DisplayAuthor.text="\(AuthorGlobal)"
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated);
let filePath = NSBundle.mainBundle().pathForResource("1", ofType: "json")
var fileContents : String = ""
do {
fileContents = try String(contentsOfFile: filePath!, encoding: NSUTF8StringEncoding)
} catch (_) {
}
var json : Dictionary<String,Array<Dictionary<String,String>>> = Dictionary()
do {
json = try NSJSONSerialization.JSONObjectWithData(fileContents.dataUsingEncoding(NSUTF8StringEncoding)!, options:NSJSONReadingOptions.AllowFragments) as! Dictionary<String, Array<Dictionary<String, String>>>
} catch (_) {
}
let array = json["NewsLetter"] as Array?
if let author = array?[0]["Author"] {
AuthorGlobal.append(author)
}
print(AuthorGlobal) // This prints - ["Tom"]
if let LastName = array?[0]["LastName"] {
print(LastName) // This prints - Holton
}
if let ArticleTitle = array?[0]["ArticleTitle"] {
//Now you got your value
print(ArticleTitle) // This prints - XcodeGhost: Apple suffers a major malware infection inside the iOS app store.
}
if let Author = array?[1]["Author"] {
//Now you got your value
print(Author) // This prints - Sam
}
if let LastName = array?[1]["LastName"] {
//Now you got your value
print(LastName) // This prints - Devaney
}
if let ArticleTitle = array?[1]["ArticleTitle"] {
//Now you got your value
print ("Article Title: " + (ArticleTitle)) // This prints - Article Title: Google is 2 Billion Lines of Code
}
}
}