How to pass JSON `null` value to `nil` value in swift? - ios

I have this json wherein hospitalNumber has value and there is instances that it returns null value. The hospitalNumber has significance since its a part of the parameter needed for the endpoint in API. Please see sample json:
{
"responseMessage": "Request successful",
"data": [
{
"hospitalNumber": null,
"patientName": "Manual Entry",
"totalAmount": 10339.8000,
"manualEntry": true
},
{
"hospitalNumber": "1111111",
"patientName": "test patient",
"totalAmount": 932.5000,
"manualEntry": false
}
]
}
And below is my APIService for the endpoint that will pull the json above.
typealias getPatientDetailsPerPayoutTaskCompletion = (_ patientDetailsPerPayout: [PatientPayoutDetails]?, _ error: NetworkError?) -> Void
//Patient procedure details per patient
//parameterName is .searchByHospitalNumber = "hospitalNumber"
static func getPatientDetailsPerPayout(periodId: Int, doctorNumber: String, parameterName: PatientParameter, hospitalNumber: String, manualEntry: Bool, completion: #escaping getPatientDetailsPerPayoutTaskCompletion) {
guard let patientDetailsPerPayoutURL = URL(string: "\(Endpoint.Patient.patientProcedureDetails)?periodId=\(periodId)&doctorNumber=\(doctorNumber)\(parameterName.rawValue)\(hospitalNumber)&manualEntry=\(manualEntry)") else {
completion(nil, .invalidURL)
return
}
let sessionManager = Alamofire.SessionManager.default
sessionManager.session.getAllTasks { (tasks) in
tasks.forEach({ $0.cancel() })
}
Alamofire.request(patientDetailsPerPayoutURL, method: .get, encoding: JSONEncoding.default).responseJSON { (response) in
print(patientDetailsPerPayoutURL)
guard HelperMethods.reachability(responseResult: response.result) else {
completion(nil, .noNetwork)
return
}
guard let statusCode = response.response?.statusCode else {
completion(nil, .noStatusCode)
return
}
switch(statusCode) {
case 200:
guard let jsonData = response.data else {
completion(nil, .invalidJSON)
return
}
let decoder = JSONDecoder()
do {
let patientDetailsPayout = try decoder.decode(RootPatientPayoutDetails.self, from: jsonData)
if (patientDetailsPayout.data?.isEmpty)! {
completion(nil, .noRecordFound)
} else {
completion(patientDetailsPayout.data, nil)
}
} catch {
completion(nil, .invalidJSON)
}
case 400: completion(nil, .badRequest)
case 404: completion(nil, .noRecordFound)
default:
print("**UNCAPTURED STATUS CODE FROM (getPatientDetailsPayout)\nSTATUS CODE: \(statusCode)")
completion(nil, .uncapturedStatusCode)
}
}
}
getPatientPayoutDetails Function
func getPerPatientPayoutDetails(from: String, manualEntry: Bool) {
//SVProgressHUD.setDefaultMaskType(.black)
//SVProgressHUD.setForegroundColor(.white)
SVProgressHUD.setBackgroundColor(.lightGray)
SVProgressHUD.show(withStatus: "Retrieving Patient Procedures")
APIService.PatientList.getPatientDetailsPerPayout(periodId: doctorPayoutWeek[3].periodId!, doctorNumber: doctorNumber, parameterName: .selectedByHospitalNumber, hospitalNumber: from, manualEntry: manualEntry) { (patientPayout, error) in
guard let patientPerPayoutDetails = patientPayout, error == nil else {
if let networkError = error {
switch networkError {
case .noRecordFound:
let alertController = UIAlertController(title: "No Record Found", message: "You don't have current payment remittance", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
case .noNetwork:
let alertController = UIAlertController(title: "No Network", message: "\(networkError.rawValue)", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
default:
let alertController = UIAlertController(title: "Error", message: "There is something went wrong. Please try again", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
}
}
SVProgressHUD.dismiss()
return
}
self.selectedPatientPayment = patientPerPayoutDetails
print(self.selectedPatientPayment)
SVProgressHUD.dismiss()
return
}
}
tableView
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch indexPath.section {
case 0: break
case 1: break
case 2:
filteredPatient = indexPath.row
let selectedpatient = patientList[filteredPatient].hospitalNumber
let selectedEntry = patientList[filteredPatient].manualEntry
self.isBrowseAll = false
getPerPatientPayoutDetails(from: selectedpatient!, manualEntry: selectedEntry)
default: break
}
}
The endpoint which requires null string in hospitalNumber when it is nil
https://sample.com/openapi/getpatientpayoutdetails?periodId=579&doctorNumber=2866&hospitalNumber=null&manualEntry=true
As you can see hospital number has an important role for the endpoint. My problem is, once the tableView has reloaded it shows the data properly but when I didSelect the cell with null hospitalNumber, my app crashes and show Found nil error since hospitalNumber has null value. Hope you understand what I am trying to explain, please help me. Thank you

Your Codable model is correct, all you need is guard-let/if-let to prevent crash:
if let selectedpatient = patientList[filteredPatient].hospitalNumber, let selectedEntry = patientList[filteredPatient].manualEntry {
self.isBrowseAll = false
getPerPatientPayoutDetails(from: selectedpatient, manualEntry: selectedEntry)
}
Updated:
If you want to create endPoint in case of nil also then use coalescing operator :
getPerPatientPayoutDetails(from: selectedpatient ?? "null", manualEntry: selectedEntry)

In didSelect do like
let selectedpatient:String! //or Int whatever type it is right not this line initializes selected patient with nil
//below line will check for nil and ALSO NULL if NULL or nil it will not reassigne selectedpatient which means selectedpatient will remain nil
if let hsptlNbr = patientList[filteredPatient].hospitalNumber as? yourDataType{
selectedpatient = hsptlNbr
}
after this you can pass this as nil or value if exist in below method
getPerPatientPayoutDetails(from: selectedpatient, manualEntry: selectedEntry)
Change func getPerPatientPayoutDetails(from: String, manualEntry: Bool)
to
func getPerPatientPayoutDetails(from: String?, manualEntry: Bool)

Related

Invalid Address in ApplePay with Stripe

I am using Apple Pay with Stripe and it works fine when there is no shipping is available.
When there is a shipping address selected it always gives invalid address error. (It works with Stripe Sandbox Key)
I am using STKPaymentContext API and follow steps from the below link.
https://stripe.com/docs/mobile/ios/basic
in the configuration, I have written this.
let config = STPPaymentConfiguration.shared()
config.requiredShippingAddressFields = [.postalAddress, .phoneNumber,.name]
Not sure what is wrong here.
Here is how it looks.
HERE IS MY CODE
extension CheckoutTableViewController:STPPaymentContextDelegate{
func paymentContext(_ paymentContext: STPPaymentContext, didUpdateShippingAddress address: STPAddress, completion: #escaping STPShippingMethodsCompletionBlock) {
guard
let buyerPostalCode = address.postalCode,
let buyerCountry = address.country,
let productId = self.productDetailsData?.productId
else{
completion(.invalid, nil, nil, nil)
return
}
guard let phone = address.phone, phone.count > 0 else {
completion(.invalid,RatesError.phoneNumberRequired,[],nil)
return
}
var shipmentItem:[String:Any] = [:]
shipmentItem["order_amount"] = self.productCost
shipmentItem["actual_weight"] = 8
shipmentItem["height"] = 7
shipmentItem["width"] = 10
shipmentItem["length"] = 13
shipmentItem["currency"] = "USD"
shipmentItem["destination_postal_code"] = buyerPostalCode
shipmentItem["destination_country_code"] = buyerCountry
shipmentItem["product_id"] = productId
shipmentItem["category"] = "fashion"
enum RatesError:Error,LocalizedError{
case NoDeliveryOptionsFound
case phoneNumberRequired
public var errorDescription: String? {
switch self {
case .NoDeliveryOptionsFound:
return "No couriers are available at the address.\nPlease try with different address."
case .phoneNumberRequired:
return "Please enter phone number."
}
}
}
fetchShippingOptions(forItem: shipmentItem, completionSuccess: {[weak self] (response) in
guard let self = `self` else {
return
}
if
let responseValue = response as? [String:Any],
let rates = responseValue["rates"] as? [[String:Any]]{
self.shippingRates = []
for rate in rates{
if let fullName = rate["courier_display_name"] as? String,
let identifier = rate["courier_id"] as? String,
let amount = rate["shipment_charge_total"] as? Double,
let detail = rate["full_description"] as? String
{
let method = PKShippingMethod()
method.amount = NSDecimalNumber.init(value: amount.currency)
method.identifier = identifier
method.label = fullName
method.detail = detail.replacingOccurrences(of: fullName, with: "")
self.shippingRates.append(method)
}
}
completion(.valid, nil, self.shippingRates, self.shippingRates.first)
}else{
completion(.invalid,RatesError.NoDeliveryOptionsFound,[],nil)
}
}) { (error) in
completion(.invalid,error,[],nil)
}
}
func paymentContextDidChange(_ paymentContext: STPPaymentContext) {
if let paymentOption = paymentContext.selectedPaymentOption {
self.lblPaymentMethod.text = paymentOption.label
} else {
self.lblPaymentMethod.text = "Select Payment"
}
if let shippingMethod = paymentContext.selectedShippingMethod {
if let selectedRate = self.shippingRates.first(where: { (method) -> Bool in
guard let leftValue = method.identifier, let rightValue = shippingMethod.identifier else{
return false
}
return leftValue == rightValue
}){
self.lblAddress.text = selectedRate.label
self.shippingCharges = Double(truncating: selectedRate.amount).currency
self.lblShippingCharges.text = "$\(shippingCharges)"
self.getStripeFees(forAmount: self.productCost + self.shippingCharges)
}
} else {
self.lblAddress.text = "Select Address"
}
self.updateTotalCost()
}
func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error) {
let alertController = UIAlertController(
title: "Error",
message: error.localizedDescription,
preferredStyle: .alert
)
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: { action in
// Need to assign to _ because optional binding loses #discardableResult value
// https://bugs.swift.org/browse/SR-1681
_ = self.navigationController?.popViewController(animated: true)
})
let retry = UIAlertAction(title: "Retry", style: .default, handler: { action in
self.paymentContext?.retryLoading()
})
alertController.addAction(cancel)
alertController.addAction(retry)
self.present(alertController, animated: true, completion: nil)
}
func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: #escaping STPPaymentStatusBlock) {
self.callPaymentIntentAPI(paymentContext, didCreatePaymentResult: paymentResult, completion: completion)
}
func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) {
OperationQueue.main.addOperation {
SVProgressHUD.dismiss()
}
let title: String
let message: String
switch status {
case .error:
title = "Error"
message = error?.localizedDescription ?? ""
UIAlertController.showAlert(withTitle: title, andMessage: message, andButtonTitles: ["Okay"]) {[weak self] (selectedIndex) in
OperationQueue.main.addOperation {
self?.navigationController?.popViewController(animated: true)
}
}
case .success:
title = "Success"
message = "Your purchase was successful!"
UIAlertController.showAlert(withTitle: title, andMessage: message, andButtonTitles: ["Okay"]) {[weak self] (selectedIndex) in
OperationQueue.main.addOperation {
self?.onPaymentCompletion?()
var isControllerFound:Bool = false
for controller in self?.navigationController?.viewControllers ?? []{
if (controller is ProductDetailsViewController) || (controller is ChatVC){
isControllerFound = true
self?.navigationController?.popToViewController(controller, animated: true)
break
}
}
if !isControllerFound{
self?.navigationController?.popViewController(animated: true)
}
}
}
case .userCancellation:
return()
#unknown default:
return()
}
}
}
Finally, I found an error.
Apple calls didUpdateShippingAddress method at the time of payment but it doesn't provide all information for security purposes. So in my case phone number validation was causing that error.
So I removed the below code from that method.
guard let phone = address.phone, phone.count > 0 else {
completion(.invalid,RatesError.phoneNumberRequired,[],nil)
return
}

How to fix invalid json issue in pulling data from API using Alamofire in swift?

I need some assistance with my codes. The json below was the original response I got from the postman.
OLD Format
{
"totalCreditedAmount": 2898.3000,
"periodId": 566,
"periodDate": "4/26/2019"
}
So I created the API service code below. It runs smoothly.
APIService
struct DoctorLatestCreditedAmount {
typealias getLatestCreditedAmountTaskCompletion = (_ latestCreditedAmount: CurrentRemittance?, _ error: NetworkError?) -> Void
static func getLatestCreditedAmount(doctorNumber: String, completion: #escaping getLatestCreditedAmountTaskCompletion) {
guard let latestCreditedAmountURL = URL(string: "\(Endpoint.LatestCreditedAmount.latestCreditedAmount)/\(doctorNumber)") else {
completion(nil, .invalidURL)
return
}
let sessionManager = Alamofire.SessionManager.default
sessionManager.session.getAllTasks { (tasks) in
tasks.forEach({ $0.cancel() })
}
Alamofire.request(latestCreditedAmountURL, method: .get, encoding: JSONEncoding.default).responseJSON { (response) in
guard HelperMethods.reachability(responseResult: response.result) else {
completion(nil, .noNetwork)
return
}
guard let statusCode = response.response?.statusCode else {
completion(nil, .noStatusCode)
return
}
switch(statusCode) {
case 200: guard let jsonData = response.data else {
completion(nil, .invalidJSON)
return
}
let decoder = JSONDecoder()
do {
let currentCreditedAmount = try decoder.decode(CurrentRemittance.self, from: jsonData)
completion(currentCreditedAmount, nil)
} catch {
completion(nil, .invalidJSON)
}
case 400: completion(nil, .badRequest)
case 404: completion(nil, .noRecordFound)
default:
print("**UNCAPTURED STATUS CODE FROM (getLatestCreditedAmount)\nSTATUS CODE: \(statusCode)")
completion(nil, .uncapturedStatusCode)
}
}
}
But when the json side had some changes regarding the response. The json format changed and now using the APIService above I encountered error. It says invalidjson since the new json format is below.
NEW Format
{
"responseMessage": "Request successful",
"data": {
"totalCreditedAmount": 2898.3000,
"periodId": 566,
"periodDate": "4/26/2019"
}
}
Edited: getTotalCreditedAmount
var currentRemittance: CurrentRemittance!
func getTotalCreditedAmount(doctorNumber: String) {
windlessSetup()
APIService.DoctorLatestCreditedAmount.getLatestCreditedAmount(doctorNumber: doctorNumber) { (remittanceDetails, error) in
guard let creditedAmountDetails = remittanceDetails, error == nil else {
if let networkError = error {
switch networkError {
case .noRecordFound:
let alertController = UIAlertController(title: “No Record Found”, message: “You don’t have current payment remittance”, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: “OK”, style: .default))
self.present(alertController, animated: true, completion: nil)
case .noNetwork:
let alertController = UIAlertController(title: “No Network”, message: “\(networkError.rawValue)“, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: “OK”, style: .default))
self.present(alertController, animated: true, completion: nil)
default:
let alertController = UIAlertController(title: “Error”, message: “There is something went wrong. Please try again”, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: “OK”, style: .default))
self.present(alertController, animated: true, completion: nil)
}
}
self.creditedView.windless.end()
self.remittanceView.windless.end()
return
}
self.currentRemittance = creditedAmountDetails
self.showLatestTotalCreditedAmount()
self.creditedView.windless.end()
self.remittanceView.windless.end()
return
}
}
Encountered Error
My problem is, how can I alter my codes for APIService so it will match to the proper NEW json format I got. I am having a hard time since I get to used to pulling the same "OLD" format. I am really new to swift and I really need assistance. Hope you can give me some of your time.
You need
// MARK: - Welcome
struct Root: Codable {
let responseMessage: String
let data: CurrentRemittance
}
// MARK: - DataClass
struct CurrentRemittance: Codable {
let totalCreditedAmount: Double
let periodId: Int
let periodDate: String
}
Decode
let res = try decoder.decode(Root.self, from: jsonData)
print(res.data)
Try the code below, it works to me in array of data:
Alamofire.request(url, method: .get).responseJSON {
response in
if response.result.isSuccess {
let dataJSON = JSON(response.result.value!)
if let datas = dataJSON["data"].arrayObject {
print(datas)
}
}
}

How to get an empty data from API using Alamofire in swift?

I need some assistance again regarding my codes. Bit confuse on how will the empty data be reflected from APIService going to ViewController.
Here's the JSON
{
"responseMessage": "No record Found",
"data": []
}
As you can see the data is nil.
Here's the APIService
typealias getDoctorPayoutSummaryTaskCompletion = (_ latestPayoutSummary: DoctorPayoutSummary?, _ error: NetworkError?) -> Void
static func getDoctorPayoutSummary(doctorNumber: String, periodId: Int, completion: #escaping getDoctorPayoutSummaryTaskCompletion) {
guard let latestPayoutSummaryURL = URL(string: "\(Endpoint.LatestCreditedAmount.latestPayoutSummary)?periodId=\(periodId)&doctorNumber=\(doctorNumber)") else {
completion(nil, .invalidURL)
return
}
let sessionManager = Alamofire.SessionManager.default
sessionManager.session.getAllTasks { (tasks) in
tasks.forEach({ $0.cancel() })
}
Alamofire.request(latestPayoutSummaryURL, method: .get, encoding: JSONEncoding.default).responseJSON { (response) in
guard HelperMethods.reachability(responseResult: response.result) else {
completion(nil, .noNetwork)
return
}
guard let statusCode = response.response?.statusCode else {
completion(nil, .noStatusCode)
return
}
switch(statusCode) {
case 200:
guard let jsonData = response.data else {
completion(nil, .invalidJSON)
return
}
let decoder = JSONDecoder()
do {
let currentPayoutSummary = try decoder.decode(RootDoctorPayoutSummary.self, from: jsonData)
print(periodId)
print(currentPayoutSummary.data ?? "data is nil")
print(currentPayoutSummary.data ?? "response is nil")
completion(currentPayoutSummary.data, nil)
} catch {
completion(nil, .invalidJSON)
print(error)
}
case 400: completion(nil, .badRequest)
case 404: completion(nil, .noRecordFound)
default:
print("**UNCAPTURED STATUS CODE FROM (getDoctorPayoutSummary)\nSTATUS CODE: \(statusCode)")
completion(nil, .uncapturedStatusCode)
}
}
}
I tried to use breakpoints to track my codes and it does print data is nil if the data is empty in the APIService side. But unfortunately the getDoctorPayoutSummary function in the ViewController side doesn't recognized if the data is empty. It just recognizes if the data is not empty and it runs smoothly.
Here's the getDoctorPayoutSummary()
func getDoctorPayoutSummary(doctorNumber: String) {
SVProgressHUD.setBackgroundColor(.lightGray)
SVProgressHUD.show(withStatus: "Processing...")
APIService.DoctorLatestCreditedAmount.getDoctorPayoutSummary(doctorNumber: doctorNumber, periodId: doctorPayoutWeek[0].periodId!) { (payoutsummary, error) in
guard let payoutSummaryDetails = payoutsummary, error == nil else {
if let networkError = error {
switch networkError {
case .noRecordFound:
self.noRecordView.isHidden = false
self.creditedAmountLabel.isHidden = true
case .noNetwork:
let alertController = UIAlertController(title: "No Network", message: "\(networkError.rawValue)", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
default:
let alertController = UIAlertController(title: "Error", message: "There is something went wrong. Please try again", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
}
}
SVProgressHUD.dismiss()
return
}
self.payoutSummary = payoutSummaryDetails
print(payoutSummaryDetails)
if self.payoutSummary == nil {
self.noRecordView.isHidden = false
SVProgressHUD.dismiss()
return
}
self.creditedAmountLabel.text = "₱\(self.payoutSummary.creditedAmount ?? 0.0)"
self.getPatientList()
self.noRecordView.isHidden = true
self.week1TableView.reloadData()
SVProgressHUD.dismiss()
return
}
}
My apology if I ask too much but I really need help to solve this one so I can move on with other tasks. Asking for your little time to help me. Thank you so much.
If data is empty it will return empty array, so check
if currentPayoutSummary.data.isEmpty {
print("Data is empty !!")
completion(nil, .noRecordFound)
}
or
if currentPayoutSummary.data.count == 0 {
print("Data is empty !!")
completion(nil, .noRecordFound)
}
Two options:
Check responseMessage
let currentPayoutSummary = try decoder.decode(RootDoctorPayoutSummary.self, from: jsonData)
if currentPayoutSummary.responseMessage == "No record Found" {
completion(nil, .noRecordFound)
} else {
completion(currentPayoutSummary.data, nil)
}
Check if data is empty
let currentPayoutSummary = try decoder.decode(RootDoctorPayoutSummary.self, from: jsonData)
if currentPayoutSummary.data.isEmpty {
completion(nil, .noRecordFound)
} else {
completion(currentPayoutSummary.data, nil)
}
Use responseData instead of responseJSON

How to fix invalid JSON response in swift when using get request in Alamofire?

I am creating an app wherein it pulls PatientList from API Server and it will display to a TableView. Upon checking, it returns 200 status code but falls to invalidJSON error. But when I checked in Postman, it returns 200 status code and pulls the records properly. I am quite confuse which part of my codes causes the error since I am new in swift. I am seeking help to solve the issue. Below are my sample codes for your references. Thank you so much in advance.
Patient.swift
struct Patient: Codable {
let hospitalNumber: Int
let patientName: String
let totalAmount: Double
enum CodingKeys: String, CodingKey {
case hospitalNumber = "hospitalNumber"
case patientName = "patientName"
case totalAmount = "totalAmount"
}
}
APIService.swift
struct PatientList {
typealias getPatientListTaskCompletion = (_ patientListperPayout: [Patient]?, _ error: NetworkError?) -> Void
static func getPatientList(doctorNumber: Int, periodId: Int, completion: #escaping getPatientListTaskCompletion) {
guard let patientPerPayoutURL = URL(string: "\(Endpoint.Patient.patientPerPayout)?periodId=\(periodId)&doctorNumber=\(doctorNumber)") else {
completion(nil, .invalidURL)
return
}
let sessionManager = Alamofire.SessionManager.default
sessionManager.session.getAllTasks { (tasks) in
tasks.forEach({ $0.cancel() })
}
Alamofire.request(patientPerPayoutURL, method: .get, encoding: JSONEncoding.default).responseJSON { (response) in
guard HelperMethods.reachability(responseResult: response.result) else {
completion(nil, .noNetwork)
return
}
guard let statusCode = response.response?.statusCode else {
completion(nil, .noStatusCode)
return
}
switch(statusCode) {
case 200:
guard let jsonData = response.data else{
completion(nil, .invalidJSON)
print(statusCode)
return
}
let decoder = JSONDecoder()
do {
let patientListArray = try decoder.decode([Patient].self, from: jsonData)
let sortedPatientListArray = patientListArray.sorted(by: { $0.patientName < $1.patientName })
completion(sortedPatientListArray, nil)
}catch{
completion(nil, .invalidJSON)
print(statusCode)
}
case 400:
completion(nil, .badRequest)
case 404:
completion(nil, .noRecordFound)
default:
print("UNCAPTURED STATUS CODE FROM getPatientList\nSTATUS CODE: \(statusCode)")
completion(nil, .uncapturedStatusCode)
}
}
}
Controller.swift
var patientList: [Patient]! {
didSet {
performSegue(withIdentifier: patientListIdentifier, sender: self)
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.latestCreditedAmountTableView.dataSource = self
self.latestCreditedAmountTableView.delegate = self
configureTableViewCell()
showTotalCreditedAmount()
getDoctorPayoutSummary(doctorNumber: doctorNumber)
}
func getDoctorPayoutSummary(doctorNumber: Int) {
self.payoutSummary = payoutSummaryDetails
self.taxRateVatRateLabel.text = "\(self.payoutSummary.taxRate) / \(self.payoutSummary.vatRate)"
self.getPatientList()
self.latestCreditedAmountTableView.reloadData()
return
}
func getPatientList() {
APIService.PatientList.getPatientList(doctorNumber: doctorNumber, periodId: currentRemittance.periodId) { (patientListArray, error) in
guard let patientListPerPayout = patientListArray, error == nil else {
if let networkError = error {
switch networkError {
case .noRecordFound:
let alertController = UIAlertController(title: "No Record Found", message: "You don't have current payment remittance", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
case .noNetwork:
let alertController = UIAlertController(title: "No Network", message: "\(networkError.rawValue)", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
default:
let alertController = UIAlertController(title: "Error", message: "There is something went wrong. Please try again", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true, completion: nil)
}
}
return
}
self.patientList = patientListPerPayout
return
}
}
JSON Response
[
{
"hospitalNumber": null,
"patientName": null,
"totalAmount": 31104
},
{
"hospitalNumber": "",
"patientName": "LastName, FirstName",
"totalAmount": 3439.8
}
]
Your JSON response shows that some of the fields can be null - hospitalNumber and patientName at least. Also hospitalNumber is a string in the JSON - thanks to #Don for pointing out. Your struct should also be able to cope with these being nullable by making the mapped fields nullable also. I.e.
struct Patient: Codable {
let hospitalNumber: String?
let patientName: String?
let totalAmount: Double
enum CodingKeys: String, CodingKey {
case hospitalNumber = "hospitalNumber"
case patientName = "patientName"
case totalAmount = "totalAmount"
}
}
You will need to do the same for totalAmount if that can ever be null also. Whether the API is correct to return null in any circumstance is of course another question - how a null hospital number or name is useful may need to be addressed.
Make sure you do not force-unwrap the fields when you use them.
Just make below changes in your model class. Define your model class variable as optional which is not mandatory from APIs.
struct Patient: Codable {
var hospitalNumber: String?
let patientName: String?
let totalAmount: Double?
enum CodingKeys: String, CodingKey {
case hospitalNumber = "hospitalNumber"
case patientName = "patientName"
case totalAmount = "totalAmount"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let hospitalNumb = try container.decode(Int?.self, forKey: .hospitalNumber) {
hospitalNumber = String(hospitalNumb)
} else {
hospitalNumber = try container.decode(String.self, forKey: .hospitalNumber)
}
patientName = try container.decode(String.self, forKey: .patientName)
totalAmount = try container.decode(Double.self, forKey: .totalAmount)
}
}
Note:
Codable OR Decodable is not working if the type is different for the same key or you can say like that type is different then specified type.

Why is my firebase database not waiting to update values

So I am really new to threading and I've been reading up on it all day. For some reason though the data isn't loading before other code executes
Basically I need all the values that have a key ["whatever"] to be filled into an array, which works in other places because I don't need to load it first. So i have checked and double checked the keys that I am updating do exist and the keys I am extracting do exist maybe not the values yet but the keys do.
The problem is the code goes to fast to through the method. How would I make the main thread wait untill my firebase has loaded the data I have tried it below but it does not seem to be working
here is my code
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let alertController = UIAlertController(title: "Accept Bet", message: "Match the bet of " + amountBets[indexPath.row], preferredStyle: .alert)
let okButton = UIAlertAction(title: "No", style: .default, handler: { (action) -> Void in
print("Ok button tapped")
})
let yesButton = UIAlertAction(title: "Yes", style: .default, handler: { (action) -> Void in
// let them know to wait a second or the bet won't go through
var waitController = UIAlertController(title: "Please Wait", message: "You must wait for the bet to go through", preferredStyle: .alert)
self.present(waitController, animated: true, completion: nil)
//take away that bitches money
self.takeAwayMoney(self.amountBets[indexPath.row], completion: { (result: Bool?) in
guard let boolResult = result else {
return
}
if boolResult == true {
self.updateBet(indexPath.row, completion: {(result: String?) in
guard let resultRecieved = result else {
return
}
print(self.opposingUserNames)
//let delayInSeconds = 7.0 // 1
//DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) { // 2
self.dismiss(animated: true, completion: nil)
let successController = UIAlertController(title: "Success", message: "You have made a bet with " + self.opposingUserNames!, preferredStyle: .alert)
let okButt = UIAlertAction(title: "Ok", style: .default, handler: nil)
successController.addAction(okButt)
self.present(successController, animated: true, completion: nil)
//lastly delete the opposing UserName
print(self.opposingUserNames)
self.amountBets.remove(at: indexPath.row)
self.tableView.reloadData()
print("Second")
print(self.opposingUserNames)
//}
})
} else {
return
}
})
//then delete that cell and do another pop up that says successful
// check if value is yes or no in the database
})
alertController.addAction(okButton)
alertController.addAction(yesButton)
present(alertController, animated: true, completion: nil)
}
The below function updates the values OpposingUsername and show
func updateBet(_ index: Int, completion: #escaping (_ something: String?) -> Void) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
// ...
self.datRef.child("Bets").observe(.childAdded, with: { snapshot in
//
// this is the unique identifier of the bet. eg, -Kfx81GvUxoHpmmMwJ9P
guard let dict = snapshot.value as? [String: AnyHashable] else {
print("failed to get dictionary from Bets.\(self.userName)")
return
}
let values = ["OpposingUsername": self.userName,"Show": "no"]
self.datRef.child("Bets").child(self.tieBetToUser[index]).updateChildValues(values)
// now get the opposing username which is just the Username registered to that specific bet
self.datRef.child("Bets").child(self.tieBetToUser[index]).observe(.childAdded, with: { snapshot in
guard let dict2 = snapshot.value as? [String: AnyHashable] else {
return
}
let userNameOfOtherPlayer = dict2["Username"] as? String
self.opposingUserNames = userNameOfOtherPlayer!
completion(self.opposingUserNames)
})
})
}) { (error) in
print(error.localizedDescription)
}
}
ok so with this updated code it cuts out the logic errors I had earlier, but now the app hangs on my waitAlertViewController. Not sure why. it does updated the bet in the firebase database so I know its working and running that code but its like never completing it all. sorry bibscy I see what you mean now
completion handlers are pretty powerful once you understand them better
//Notice that I made `result: String?` optional, it may or may not have a value.
func getOpoosingUserNames(_ username: String,_ index: Int, completion: #escaping (_ result: String?) -> Void ) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let username = value?["username"] as? String ?? ""
self.userName = username
// ...
self.datRef.child("Bets").observe(.childAdded, with: { snapshot in
//
// this is the unique identifier of the bet. eg, -Kfx81GvUxoHpmmMwJ9P
let betId = snapshot.key as String
guard let dict = snapshot.value as? [String: AnyHashable] else {
print("failed to get dictionary from Bets.\(self.userName)")
return
}
if let show = dict["Show"] as? String {
let opposingUser = dict["OpposingUsername"] as? String
self.opposingUserNames.append(opposingUser!)
}
completion(opposingUserNames)
})
}) { (error) in
print(error.localizedDescription)
}
}
//update the be
func updateBet(_ index: Int, completion: #escaping (_ something: [String]?) -> Void) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
// ...
self.datRef.child("Bets").observe(.childAdded, with: { snapshot in
//
// this is the unique identifier of the bet. eg, -Kfx81GvUxoHpmmMwJ9P
guard let dict = snapshot.value as? [String: AnyHashable] else {
print("failed to get dictionary from Bets.\(self.userName)")
return
}
let values = ["OpposingUsername": self.userName,"Show": "no"]
//store the values received from Firebase in let valueOfUpdate and pass this
// constant to your completion handler completion(valueOfUpdate) so that you can use this value in func
//tableView(_ tableView:_, didSelectRowAt indexPath:_)
let valueOfUpdate = self.datRef.child("Bets").child(self.tieBetToUser[index]).updateChildValues(values)
completion(valueOfUpdate)
}) { (error) in
print(error.localizedDescription)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let alertController = UIAlertController(title: "Accept Bet", message: "Match the bet of " + amountBets[indexPath.row], preferredStyle: .alert)
let okButton = UIAlertAction(title: "No", style: .default, handler: { (action) -> Void in
print("Ok button tapped")
})
let yesButton = UIAlertAction(title: "Yes", style: .default, handler: { (action) -> Void in
//take away that bitches money
self.takeAwayMoney(self.amountBets[indexPath.row])
//then delete that cell and do another pop up that says successful
// check if value is yes or no in the database
self.updateBet(indexPath.row, completion: {(result: String) in
guard let resultReceivedInupdateBet = result, else {
print("result of updateBet() is \(result)")
}
print("If you see this print, database was updated")
//calling this method with the indexPath.row clicked by the user
self.getOpoosingUserNames(self.userName, indexPath.row, completion: { (result: [String]) in
guard let resultReceivedIngetOpoosingUserNames = result{
print("result of getOpoosingUserNames is \(result)")
}
print("If you see this print, you received a value from db after calling getOpoosingUserNames and that value is in \(result) ")
//result is not nil, resultReceivedIngetOpoosingUserNames has the same value as result.
}//end of self.getOpoosingUserNames
self.checkForNo(indexPath.row)
self.amountBets.remove(at: indexPath.row)
self.tableView.reloadData()
print(self.opposingUserNames)
let successController = UIAlertController(title: "Success", message: "You have made a bet with " + self.opposingUserNames[indexPath.row], preferredStyle: .alert)
let okButt = UIAlertAction(title: "Ok", style: .default, handler: nil)
successController.addAction(okButt)
self.present(successController, animated: true, completion: nil)
//lastly delete the opposing UserName
self.opposingUserNames.remove(at: indexPath.row)
})
alertController.addAction(okButton)
alertController.addAction(yesButton)
present(alertController, animated: true, completion: nil)
}

Resources