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

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()
}
}

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)

What is the best possible way to send custom error responses in .net core web api

I'm making a .net Core WebApi using .Net Core 2.2. The API is ready but the failure message and response is where I'm stuck at.
Right now, I'm getting respose like below
json
{
"empId":1999,
"empName":"Conroy, Deborah",
"enrollmentStatus":true,
"primaryFingerprintScore":65,
"secondaryFingerprintScore":60,
"primaryFingerprint":null,
"secondaryFingerprint":null,
"primaryFingerprintType":null,
"secondaryFingerprintType":null}
}
I created a json formatter class and wrote the below code
public class SuperJsonOutputFormatter : JsonOutputFormatter
{
public SuperJsonOutputFormatter(
JsonSerializerSettings serializerSettings,
ArrayPool<char> charPool) : base(serializerSettings, charPool)
{
}
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context,
Encoding selectedEncoding)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (selectedEncoding == null)
throw new ArgumentNullException(nameof(selectedEncoding));
using (TextWriter writer =
context.WriterFactory(
context.HttpContext.Response.Body,
selectedEncoding))
{
var rewrittenValue = new
{
resultCode = context.HttpContext.Response.StatusCode,
resultMessage =
((HttpStatusCode)context.HttpContext.Response.StatusCode)
.ToString(),
result = context.Object
};
this.WriteObject(writer, rewrittenValue);
await writer.FlushAsync();
}
}
I expect all the error codes to be sent as generic error messages like the JSON below.
FOR STATUS OKAY:
{
"status" : True,
"error" : null,
"data" : {
{
"empId":1999,
"empName":"Conroy, Deborah",
"enrollmentStatus":true,
"primaryFingerprintScore":65,
"secondaryFingerprintScore":60,
"primaryFingerprint":null,
"secondaryFingerprint":null,
"primaryFingerprintType":null,
"secondaryFingerprintType":null}
}
}
}
FOR OTHER STATUS LIKE 404, 500, 400, 204
{
"status" : False,
"error" : {
"error code" : 404,
"error description" : Not Found
},
"data" : null
}
I expect all the error codes to be sent as generic error messages like the JSON below
You're almost there. What you need to do is enabling your SuperJsonOutputFormatter.
A Little Change to Your Formatter
Firstly, your formatter didn't return a json with the same schema as you want. So I create a dummy class to hold the information for error code and error description:
public class ErrorDescription{
public ErrorDescription(HttpStatusCode statusCode)
{
this.Code = (int)statusCode;
this.Description = statusCode.ToString();
}
[JsonProperty("error code")]
public int Code {get;set;}
[JsonProperty("error description")]
public string Description {get;set;}
}
And change your WriteResponseBodyAsync() method as below:
...
using (TextWriter writer = context.WriterFactory(context.HttpContext.Response.Body, selectedEncoding)) {
var statusCode = context.HttpContext.Response.StatusCode;
var rewrittenValue = new {
status = IsSucceeded(statusCode),
error = IsSucceeded(statusCode) ? null : new ErrorDescription((HttpStatusCode)statusCode),
data = context.Object,
};
this.WriteObject(writer, rewrittenValue);
await writer.FlushAsync();
}
Here the IsSucceeded(statusCode) is a simple helper method that you can custom as you need:
private bool IsSucceeded(int statusCode){
// I don't think 204 indicates that's an error.
// However, you could comment out it if you like
if(statusCode >= 400 /* || statusCode==204 */ ) { return false; }
return true;
}
Enable your Formatter
Secondly, to enable your custom Formatter, you have two approaches: One way is to register it as an global Formatter, the other way is to enable it for particular Controller or Action. Personally, I believe the 2nd way is better. So I create a Action Filter to enable your formatter.
Here's an implementation of the Filter that enables your custom formatter dynamically:
public class SuperJsonOutputFormatterFilter : IAsyncActionFilter{
private readonly SuperJsonOutputFormatter _formatter;
// inject your SuperJsonOutputFormatter service
public SuperJsonOutputFormatterFilter(SuperJsonOutputFormatter formatter){
this._formatter = formatter;
}
// a helper method that provides an ObjectResult wrapper over the raw object
private ObjectResult WrapObjectResult(ActionExecutedContext context, object obj){
var wrapper = new ObjectResult(obj);
wrapper.Formatters.Add(this._formatter);
context.Result= wrapper;
return wrapper;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
ActionExecutedContext resultContext = await next();
// in case we get a 500
if(resultContext.Exception != null && ! resultContext.ExceptionHandled){
var ewrapper = this.WrapObjectResult(resultContext, new {});
ewrapper.StatusCode = (int) HttpStatusCode.InternalServerError;
resultContext.ExceptionHandled = true;
return;
}
else {
switch(resultContext.Result){
case BadRequestObjectResult b : // 400 with an object
var bwrapper=this.WrapObjectResult(resultContext,b.Value);
bwrapper.StatusCode = b.StatusCode;
break;
case NotFoundObjectResult n : // 404 with an object
var nwrapper=this.WrapObjectResult(resultContext,n.Value);
nwrapper.StatusCode = n.StatusCode;
break;
case ObjectResult o : // plain object
this.WrapObjectResult(resultContext,o.Value);
break;
case JsonResult j : // plain json
this.WrapObjectResult(resultContext,j.Value);
break;
case StatusCodeResult s: // other statusCodeResult(including NotFound,NoContent,...), you might want to custom this case
var swrapper = this.WrapObjectResult(resultContext, new {});
swrapper.StatusCode = s.StatusCode;
break;
}
}
}
}
And don't forget to register your formatter as a service :
services.AddScoped<SuperJsonOutputFormatter>();
Finally, when you want to enable your formatter, just add a [TypeFilter(typeof(SuperJsonOutputFormatterFilter))] annotation for the controller or action.
Demo
Let's create an action method for Test:
[TypeFilter(typeof(SuperJsonOutputFormatterFilter))]
public IActionResult Test(int status)
{
// test json result(200)
if(status == 200){ return Json(new { Id = 1, }); }
// test 400 object result
else if(status == 400){ return BadRequest( new {}); }
// test 404 object result
else if(status == 404){ return NotFound(new { Id = 1, }); }
// test exception
else if(status == 500){ throw new Exception("unexpected exception"); }
// test status code result
else if(status == 204){ return new StatusCodeResult(204); }
// test normal object result(200)
var raw = new ObjectResult(new XModel{
empId=1999,
empName = "Conroy, Deborah",
enrollmentStatus=true,
primaryFingerprintScore=65,
secondaryFingerprintScore=60,
primaryFingerprint = null,
secondaryFingerprint= null,
primaryFingerprintType=null,
secondaryFingerprintType=null
});
return raw;
}
Screenshot:

RxAlamofire - how to get the response on error?

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

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.

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
}
}
}