I am creating an app which requires to pull data from API, the scenario is, I will get the json data below:
[
{
"chargeId": "33fbbbd0-2e33-11e9-a2cb-8a27ecbbcb73",
"chargeDate": "2019-02-12T03:28:44.000",
"vatRate": "NON-VAT",
"taxRate": 0.1,
"policyGroup": "Patient Participation",
"itemDescription": "Ecg at er/icu df",
"scdFlag": 0,
"amounts": null,
"scdDiscounts": 0,
"othDiscounts": 4.54,
"adjustment": 0,
"pfBill": 222.46,
"vatAmount": 0,
"taxableAmount": 11.12,
"merchantDiscount": 0,
"creditedAmount": 211.3,
"chargeAmount": null,
"previousCredits": null
},
{
"chargeId": "5a2cabc1-46c9-11e9-a2cf-863c7cdffd18",
"chargeDate": "2019-03-15T10:24:21.000",
"vatRate": "NON-VAT",
"taxRate": 0.1,
"policyGroup": "Patient Participation",
"itemDescription": "Professional Fees",
"scdFlag": 0,
"amounts": null,
"scdDiscounts": 0,
"othDiscounts": 0,
"adjustment": 0,
"pfBill": 1000,
"vatAmount": 0,
"taxableAmount": 100,
"merchantDiscount": 0,
"creditedAmount": 900,
"chargeAmount": null,
"previousCredits": null
}
]
I did pulled the data successfully by using the Alamofire code below:
typealias getPatientDetailsPerPayoutTaskCompletion = (_ patientDetailsPerPayout: [PatientPayoutDetails]?, _ error: NetworkError?) -> Void
static func getPatientDetailsPerPayout(periodId: Int, doctorNumber: String, parameterName: PatientParameter, hospitalNumber: String, completion: #escaping getPatientDetailsPerPayoutTaskCompletion) {
guard let patientDetailsPerPayoutURL = URL(string: "\(Endpoint.Patient.patientPayoutDetails)?periodId=\(periodId)&doctorNumber=\(doctorNumber)\(parameterName.rawValue)\(hospitalNumber)") 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([PatientPayoutDetails].self, from: jsonData)
completion(patientDetailsPayout, 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)
}
}
The JSON Data will display in a CollectionCell, and user will tapped the cell to view the data under one chargedId but unfortunately, when I tapped the cell the all data are pulled instead of one part of the array only. The code below is what I used to pull just part of the array:
typealias getSelectedPatientItemDetailsTaskCompletion = (_ selectedpatient: PatientPaymentDetails?, _ error: NetworkError?) -> Void
static func getPatientItemDetails(periodId: Int, doctorNumber: String, parameterName: PatientParameter, hospitalNumber: String, chargeId: String, completion: #escaping getSelectedPatientItemDetailsTaskCompletion) {
guard let patientDetailsPerPayoutURL = URL(string: "\(Endpoint.Patient.patientPayoutDetails)?periodId=\(periodId)&doctorNumber=\(doctorNumber)\(parameterName.rawValue)\(hospitalNumber)") 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(PatientPaymentDetails.self, from: jsonData)
completion(patientDetailsPayout, 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)
}
}
}
}
}
Didselect Function to pull data
switch indexPath.section {
case 0:
self.selectedCardIndex = indexPath
let selectedItem = selectedItemDescription.id
getItemDetails(parameter: .searchByChargedId, from: selectedItem)
let cardController = UserCardViewController.init(nibName: "UserCardViewController", bundle: nil)
present(cardController, animated: true, completion: nil)
default: break
}
}
getItemDetails Function
func getItemDetails(parameter: PatientParameter, from: String) {
APIService.PatientList.getPatientItemDetails(periodId: currentRemittance.periodId, doctorNumber: doctorNumber, parameterName: parameter, hospitalNumber: patient.hospitalNumber!, chargeId: from) { (getItem, error) in
guard let pageItemDescription = getItem, error == nil else {
SVProgressHUD.dismiss()
return
}
switch parameter {
case .selectedByChargedID:
if self.patientPayoutDetails == nil {
self.selectedPatientItemDescription = pageItemDescription
}else {
self.patientPayoutDetails.append(contentsOf: pageItemDescription.chargedId)
}
default: break
}
SVProgressHUD.dismiss()
}
}
Hope you can help me, sorry if I included almost all the code but I just want to show you the flow of my codes. Been working on it for almost 1 week. Thank you.
Both of your functions getPatientItemDetails and getPatientDetailsPerPayout are retrieving data from the same URL:
guard let patientDetailsPerPayoutURL = URL(string: "\(Endpoint.Patient.patientPayoutDetails)?periodId=\(periodId)&doctorNumber=\(doctorNumber)\(parameterName.rawValue)\(hospitalNumber)") else {
You probably have different URL for different endpoints; verify the URL for both methods making sure you use the appropriate ones.
Related
I am fetching data for different json models from multiple functions:
func fetchMovies(from moviesUrl: String, _ completion: #escaping (NowPlayingResponse?) -> Void){
guard let safeUrl = URL(string: moviesUrl + apiKey) else {return}
let alamofireRequest = AF.request(safeUrl)
alamofireRequest.responseDecodable(of: NowPlayingResponse.self, decoder: jsonDecoder){ response in
switch response.result {
case .success:
guard let safeMovies = response.value else {
completion(nil)
return
}
completion(safeMovies)
case .failure(let error):
print(error)
completion(nil)
}
}
}
public func fetchGenres(from genresUrl: String, _ completion: #escaping (GenresResponse?) -> Void) {
guard let safeUrl = URL(string: genresUrl + apiKey) else {return}
let alamofireRequest = AF.request(safeUrl)
alamofireRequest.responseDecodable(of: GenresResponse.self, decoder: jsonDecoder){ response in
switch response.result {
case .success:
guard let safeGenres = response.value else {
completion(nil)
return
}
completion(safeGenres)
case .failure(let error):
print(error)
completion(nil)
}
}
}
I tried to make one function which will handle all types so it made my code cleaner:
func fetchMovies<T: Codable>(from moviesUrl: String, _ completion: #escaping (T?) -> Void){
guard let safeUrl = URL(string: moviesUrl + apiKey) else {return}
let alamofireRequest = AF.request(safeUrl)
alamofireRequest.responseDecodable(of: T.self, decoder: jsonDecoder){ response in
switch response.result {
case .success:
guard let safeMovies = response.value else {
completion(nil)
return
}
completion(safeMovies)
case .failure(let error):
print(error)
completion(nil)
}
}
}
When I called function to fetch data:
func getMoviees(){
MovieServiceAPI.shared.fetchMovies(from: "https://api.themoviedb.org/3/movie/now_playing") { (moviesResponse) in
guard let safeMovies = moviesResponse?.results else {return}
self.movieList = safeMovies
DispatchQueue.main.async { [unowned self] in
hideLoader()
self.tableView.reloadData()
}
}
}
I am getting Generic parameter 'T' could not be inferred error. I tried to put MovieServiceAPI.shared.fetchMovies<NowPlayingReponse: Codable> where NowPlayingResponse represents json model for data. Where am I getting wrong?
I am trying to parse data from the website movieDatabase.com However there's some issue decoding the data to json and populating my table view.I am not sure why this is happening. Please I need help spotting out the problem. Here's my code. https://github.com/lexypaul13/Movie-Browser/tree/main/Movie-Browser
struct Movies: Codable {
let overview:String?
let original_title: String?
let poster_path:String
}
struct ApiResponse:Codable, Hashable {
let page:Int
let shows:[Movies]
enum CodingKeys:String, CodingKey {
case page = "page"
case shows = "results"
}
}
class NetworkManger{
enum EndPoint{
case showList
}
static let shared = NetworkManger()
private let baseURL : String
private var apiKeyPathCompononent :String
private init(){
self.baseURL = "https://api.themoviedb.org/3/movie/now_playing?"
self.apiKeyPathCompononent = "api_key=a07e22bc18f5cb106bfe4cc1f83ad8ed"
}
private var jsonDecoder:JSONDecoder = {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}()
func get<T:Decodable>(_ endPoints: EndPoint, urlString: String, completed:#escaping(Result<T?,ErroMessage>)->Void){
guard let url = urlBuilder(endPoint: endPoints) else {
completed(.failure(.invalidURL))
return
}
let task = URLSession.shared.dataTask(with: url){ data, response, error in
if let _ = error {
completed(.failure(.unableToComplete))
return
}
guard let response = response as? HTTPURLResponse, response.statusCode==200 else {
print(ErroMessage.invalidResponse.rawValue)
completed(.failure(.invalidResponse))
return
}
guard let data = data else{
completed(.failure(.invalidData))
return
}
do{
let apiResponse = try self.jsonDecoder.decode([T].self, from: data)
DispatchQueue.main.async {
completed(.success(apiResponse as? T))
}
} catch{
print(ErroMessage.invalidData.rawValue)
}
}
task.resume()
}
private func urlBuilder(endPoint:EndPoint )->URL?{
switch endPoint {
case .showList:
return URL(string: baseURL + apiKeyPathCompononent )
}
}
func getMovies(){
NetworkManger.shared.get(.showList, urlString: "") { [weak self] (result: Result<[Movies]?,ErroMessage> ) in
guard let self = self else { return }
switch result{
case .success(let movies):
self.movies = movies ?? []
DispatchQueue.main.async {self.tableView.reloadData()}
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
The root object returned from the api is your ApiResult struct. This contains an array of movies (which you have mapped to the shows property of the ApiResult)
You need to change the getMovies function so that the right generic type can be inferred and the json decoder can do the right thing
func getMovies(){
NetworkManger.shared.get(.showList, urlString: "") { [weak self] (result: Result<ApiResult,ErroMessage> ) in
guard let self = self else { return }
switch result{
case .success(let apiResult):
self.movies = apiResult.shows
DispatchQueue.main.async {self.tableView.reloadData()}
case .failure(let error):
print(error.localizedDescription)
}
}
}
Here i am extracting data as DataModel. But i want to make this class generic and pass the model myself so that i can use it to parse data from multiple API's. Can Anyone Help?
import Foundation
struct NetworkManager {
func fetchData(url : String, completion : #escaping (DataModel?) -> ()) {
print("Neeraj here")
let sessionURL = URL(string: url)
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: sessionURL!) { (data, response, error) in
if error == nil {
if let safeData = data {
if let parsedData = self.parseData(data : safeData) {
print("got data")
completion(parsedData)
}
else {
debugPrint("failed to fetch data")
completion(nil)
}
}
}
else {
print("error in data task is \(String(describing: error))")
completion(nil)
}
}
dataTask.resume()
}
func parseData(data : Data) -> DataModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(DataModel.self, from: data)
return decodedData
} catch {
print("error while parsing data \(error)")
return nil
}
}
}
With the convenient Result type you can write a quite tiny generic method, it returns the decoded type on success and any error on failure
func fetchData<T: Decodable>(urlString: String, completion: #escaping (Result<T,Error>) -> Void) {
guard let url = URL(string: urlString) else { return } // or throw an error
URLSession.shared.dataTask(with: url) { (data, _, error) in
if let error = error { completion(.failure(error)); return }
completion( Result{ try JSONDecoder().decode(T.self, from: data!) })
}.resume()
}
Note: Force unwrapping data! is 100% safe if no error occurs
Be aware that you have to specify the concrete type when you are going to call the method
fetchData(urlString: "https://example.com/api") { (result : Result<MyModel,Error>) in
switch result {
case .success(let model): print(model)
case .failure(let error): print(error)
}
}
You can add a generic type constraint (called Model) which conforms Decodable like below:
struct NetworkManager {
func fetchData<Model: Decodable>(url : String, completion : #escaping (Model?) -> ()) {
let sessionURL = URL(string: url)
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: sessionURL!) { (data, response, error) in
if error == nil {
if let safeData = data {
do {
let decodedData = try JSONDecoder().decode(Model.self, from: safeData)
completion(decodedData)
} catch {
print("error while parsing data \(error)")
}
} else {
debugPrint("failed to fetch data")
completion(nil)
}
}
else {
print("error in data task is \(String(describing: error))")
completion(nil)
}
}
dataTask.resume()
}
}
Usage
struct SampleModel: Decodable {
let name: String
}
NetworkManager().fetchData(url: "") { (data: SampleModel?) in
print(data)
}
You can write a generic function to fetch data like this one :
func fetchGenericData<T: Decodable>(urlString: String, completion: #escaping (T) -> ()) {
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) { (data, resp, err) in
if let err = err {
print("Failed to fetch data:", err)
return
}
guard let data = data else { return }
do {
let obj = try JSONDecoder().decode(T.self, from: data)
completion(obj)
} catch let jsonErr {
print("Failed to decode json:", jsonErr)
}
}.resume()
}
}
I suppose that you have a data model, if you have not, you should create for your every object. Also by using a dummy URL i will make a request and fetch the JSON includes some users name and ids with JSON format.
Let`s define a data model for this:
struct StackUser: Decodable {
let id: Int
let name: String
}
fetchGenericData(urlString: "https://api.stackoverexample.com/stackusers") { (stackUsers: [StackUser]) in
stackUsers.forEach({print($0.name)})
}
Finally you will be parse the data and prints like this:
Rob
Matt
Vadian
I have been frustrated with this problem. my button not firing when posting with alamofire, but when I test to print the button if it working or not. it is working, what went wrong actually I don't know what is the solution anyway? here I show you my code please help me
#objc func handleSave() {
print("save")
guard let fullName = infos?.fullName else { return }
guard let email = infos?.email else { return }
guard let phoneNumber = infos?.phoneNumber else { return }
guard let city = infos?.pobId else { return }
guard let birth = infos?.dob else { return }
guard let religion = infos?.religionId else { return }
guard let imageName = userDefaults.string(forKey: "profileImage") else { return }
guard let src = userDefaults.string(forKey: "src") else { return }
let parameter: [String: Any] = [
"full_name": fullName,
"email": email,
"pob_id": city,
"dob": birth,
"phone_number": phoneNumber,
"religion_id": religion,
"asset": [
"id": UUID().uuidString,
"filename": imageName,
"content_type": "image/png",
"src": src
]
]
progressHUD.show(in: self.view)
BasicInfoServices.shared.postBasicInfo(parameters: parameter) { (success) in
if success {
self.progressHUD.dismiss(animated: true)
self.presentGAlertOnMainThread(image: #imageLiteral(resourceName: "ic-message-success"), title: "Success".localized(), message: "Success save profile".localized(), completion: nil)
} else {
self.progressHUD.dismiss(animated: true)
self.presentGAlertOnMainThread(image: #imageLiteral(resourceName: "ic-message-failure"), title: "Failed".localized(), message: "Failed to save profile".localized(), completion: nil)
}
}
}
// This is my network services
func postBasicInfo(parameters: [String: Any], completion: #escaping (Bool) -> Void) {
AF.request(API_URL.BASIC_INFO.USER, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: HEADERS, interceptor: nil).responseData(completionHandler: { responseData in
debugPrint(responseData)
if responseData.error == nil {
completion(true)
} else {
completion(false)
print("Error post info")
return
}
guard let response = responseData.response, response.statusCode == 200 else {
completion(false)
print("Failed response")
return
}
guard let data = responseData.data else {
completion(false)
print("Failed to fetch data")
return
}
})
}
I'm having trouble with the ResponseSerializer I get an unresolved identifier and for Response I get an undeclared type. I've read from alamofire migration doc that Response has been changed to multiple types. So I should change Response->DataReponse but this means I can only pass one argument like:
// What I have
Response(<ListWrapper, NSError>)
// What I should change it to?
DataResponse(<ListWrapper>)
How can I still recieve the Error this way and more importantly how do I migrate the extension to alamofire 4?
My class:
class List{
var idNumber: String?
var title: String?
var posterPath: String?
var release: String?
required init(json: JSON, id: Int?)
{
self.idNumber = json[ListFields.Id.rawValue].stringValue
self.title = json[ListFields.Title.rawValue].stringValue
self.posterPath = json[ListFields.PosterPath.rawValue].stringValue
self.release = json[ListFields.Release.rawValue].stringValue
}
class func setURL_APPEND(_ url: String)
{
URL_APPEND = url
}
// MARK: Endpoints
class func endpointForList() -> String
{
return URL_APPEND
}
fileprivate class func getListAtPath(_ path: String, completionHandler: #escaping (ListWrapper?, NSError?) -> Void) {
Alamofire.request(path)
.responseListArray { response in
if let error = response.result.error
{
completionHandler(nil, error)
return
}
completionHandler(response.result.value, nil)
}
}
class func getList(_ completionHandler: #escaping (ListWrapper?, NSError?) -> Void)
{
getListAtPath(List.endpointForList(), completionHandler: completionHandler)
}
}
// Problem is here:
// for ResponseSerializer I get an unresolved identifier
// and for Response I get an undeclared type
extension Alamofire.Request {
func responseListArray(_ completionHandler: #escaping (Response<ListWrapper, NSError>) -> Void) -> Self {
let responseSerializer = ResponseSerializer<ListWrapper, NSError> { request, response, data, error in
guard error == nil else
{
return .failure(error!)
}
guard let responseData = data else {
let failureReason = "Array could not be serialized because input data was nil."
let error = Alamofire.Error.errorWithCode(.dataSerializationFailed, failureReason: failureReason)
return .failure(error)
}
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .allowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, responseData, error)
switch result {
case .success(let value):
let json = SwiftyJSON3.JSON(value)
let wrapper = ListWrapper()
var allList:Array = Array<List>()
wrapper.totalCount = json["favorite_count"].intValue
// print(json)
let results = json["items"]
// print(results)
for jsonList in results
{
//print(jsonList.1)
let list = List(json: jsonList.1, id: Int(jsonList.0) )
if (list.posterPath == "")
{
continue
}
else
{
//print(movies.posterPath)
allList.append(list)
}
}
wrapper.results = allList
return .success(wrapper)
case .failure(let error):
return .failure(error)
}
}
return response(responseSerializer: responseSerializer,completionHandler: completionHandler)
}
}
Bro try below code see:
func responseListArray(_ completionHandler: #escaping (Response<ListWrapper>) -> Void) -> Self {
let responseSerializer = ResponseSerializer<ListWrapper> { request, response, data, error in
guard error == nil else
{
return .failure(error!)
}
guard let responseData = data else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNil))
}
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .allowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, responseData, error)
switch result {
case .success(let value):
let json = SwiftyJSON3.JSON(value)
let wrapper = ListWrapper()
var allList:Array = Array<List>()
wrapper.totalCount = json["favorite_count"].intValue
// print(json)
let results = json["items"]
// print(results)
for jsonList in results
{
//print(jsonList.1)
let list = List(json: jsonList.1, id: Int(jsonList.0) )
if (list.posterPath == "")
{
continue
}
else
{
//print(movies.posterPath)
allList.append(list)
}
}
wrapper.results = allList
return .success(wrapper)
case .failure(let error):
return .failure(error)
}
}
return response(responseSerializer: responseSerializer,completionHandler: completionHandler)
}