Is there anyway to access the Firebase Topics a user is subscribed to? - swift

I'm currently trying to migrate my app to firebase and I'm looking for the Firebase equivalent of Parse Installations and Channels.
What I've found is that we are supposed to use topics however in my app "subscribing" and "unsubscribing" to topics is common yet there is no way (that I have found) to see what topics a user is subscribed to. Any ideas?
I've looked through the Firebase documentation but I'm new to Firebase so maybe someone with more experience would know: https://firebase.google.com/docs/cloud-messaging/android/topic-messaging#managing_topic_subscriptions_from_the_server
Thanks in advance for any help!

FCM topic subscriptions are based on an application's Instance ID, so when you subscribe or unsubscribe to or from a topic the IID is used.
You can use the Instance ID API to get information about a particular IID, this information includes the topics that the IID is currently subscribed to. See the reference

You have to make a request to the Instance ID API. I created an extension
https://gist.github.com/eduardo22i/0ee8960c36659b17fbc538964e929cac
extension Messaging {
static private let accessToken = "" //Server Web Key
struct Topic : Decodable {
var name : String?
var addDate : String?
}
struct Rel : Decodable {
var topics = [Topic]()
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let relContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .rel)
let topicsContainer = try relContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .topics )
for key in topicsContainer.allKeys {
var topic = Topic()
topic.name = key.stringValue
let topicContainer = try? topicsContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: key)
topic.addDate = try! topicContainer?.decode(String.self, forKey: .addDate)
self.topics.append(topic)
}
}
struct CodingKeys : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
static let rel = CodingKeys(stringValue: "rel")!
static let topics = CodingKeys(stringValue: "topics")!
static let addDate = CodingKeys(stringValue: "addDate")!
}
}
func loadTopics(block : #escaping (_ topics: [Messaging.Topic]?, _ error: Error?) -> Void ) {
if let token = InstanceID.instanceID().token() {
let url = URL(string: "https://iid.googleapis.com/iid/info/\(token)?details=true")!
var request = URLRequest(url: url)
request.addValue("key=\(Messaging.accessToken)", forHTTPHeaderField: "Authorization")
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
DataManager.shared.make(request: urlRequest, block: { (data, error) in
if let data = data {
let decoder = JSONDecoder()
let rel = try? decoder.decode(Rel.self, from: data)
block(rel?.topics, error)
} else {
block(nil, error)
}
}
dataTask.resume()
}
}
}
// Usage
Messaging.messaging().loadTopics { (topics, error) in
topics?.forEach({ (topic) in
print(topic.name)
})
}

Here is the update from #Eduardo's, requested by #Nico
2 things:
You need to import Firebase, since you'll need the InstanceID and the messaging module.
Make sure you fill out the API key (accessToken), which can be found under:
(gear-next-to-project-name) > Project Settings > Cloud Messaging -> Server Key is the API key.
This is the updated code:
import Firebase
// Grab susbcribed channels
// Source 1: https://stackoverflow.com/questions/38948720/is-there-anyway-to-access-the-firebase-topics-a-user-is-subscribed-to
// Source 2: https://developers.google.com/instance-id/reference/server#get_information_about_app_instances
extension Messaging {
// Needs to be grabbed from: https://stackoverflow.com/questions/37337512/where-can-i-find-the-api-key-for-firebase-cloud-messaging
static private let accessToken = ""
struct Topic : Decodable {
var name : String?
var addDate : String?
}
struct Rel : Decodable {
var topics = [Topic]()
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let relContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .rel)
let topicsContainer = try relContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .topics )
for key in topicsContainer.allKeys {
var topic = Topic()
topic.name = key.stringValue
let topicContainer = try? topicsContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: key)
topic.addDate = try! topicContainer?.decode(String.self, forKey: .addDate)
self.topics.append(topic)
}
}
struct CodingKeys : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
static let rel = CodingKeys(stringValue: "rel")!
static let topics = CodingKeys(stringValue: "topics")!
static let addDate = CodingKeys(stringValue: "addDate")!
}
}
func loadTopics(block : #escaping (_ topics: [Messaging.Topic]?, _ error: Error?) -> Void ) {
InstanceID.instanceID().instanceID { (result, error) in
if let err = error {
block(nil, err)
} else if let result = result {
let url = URL(string: "https://iid.googleapis.com/iid/info/\(result.token)?details=true")!
var request = URLRequest(url: url)
request.addValue("key=\(Messaging.accessToken)", forHTTPHeaderField: "Authorization")
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let data = data {
let decoder = JSONDecoder()
let rel = try? decoder.decode(Rel.self, from: data)
block(rel?.topics, error)
} else {
block(nil, error)
}
}
dataTask.resume()
}
}
}
}
Usage is like this:
Messaging.messaging().loadTopics { (topics, error) in
topics?.forEach({ (topic) in
print("Subscribed to topic: \(topic.name ?? "No name")")
})
}

Related

How to make an API call with Swift?

So I'm practising trying to make API calls with Swift. The code is as below:
struct Example: Codable {
let userID: String
let ID: String
let title: String
let completed: String
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
let decoder = JSONDecoder()
if let json: Example = try? decoder.decode(Example.self, from: data) {
completion(json)
}
}
}.resume()
}
}
getJson() { (json) in
print(json.ID)
}
However, I am unable to print out anything when getJson is called.
The example API I used is found here.
The tutorial that I used to help me write the code is found here.
Modified your variable name and data type exactly as your API response.
struct Example: Codable {
let userId: Int
let id: Int
let title: String
let completed: Bool
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
let decoder = JSONDecoder()
do {
let json: Example = try! decoder.decode(Example.self, from: data)
completion(json)
}catch let error {
print(error.localizedDescription)
}
}
}.resume()
}
}
getJson() { (json) in
print(json.id)
}
You can also use CodingKey and can change your response during the init period.
struct Example: Codable {
var userID: Int
var ID: Int
var title: String
var completed: Bool
enum CodingKeys: String, CodingKey {
case userID = "userId"
case ID = "id"
case title = "title"
case completed = "completed"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
userID = try values.decode(Int.self, forKey: .userID)
ID = try values.decode(Int.self, forKey: .ID)
title = try values.decode(String.self, forKey: .title)
completed = try values.decode(Bool.self, forKey: .completed)
title = "Title: \(title)"
}
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
do {
let json: Example = try JSONDecoder().decode(Example.self, from: data)
completion(json)
}catch let error {
print(error.localizedDescription)
}
}
}.resume()
}
}
getJson() { (json) in
print("ID: \(json.ID) \(json.title)")
}
import UIKit
class ApiManager: NSObject {
static func postApiRequest(url: String,parameters:[String: Any],completion: #escaping([String: Any])-> Void) {
let session = URLSession.shared
let request = NSMutableURLRequest(url: NSURL(string: url)! as URL)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do{
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: JSONSerialization.WritingOptions())
let task = session.dataTask(with: request as URLRequest as URLRequest, completionHandler: {(data, response, error) in
if let response = response {
let nsHTTPResponse = response as! HTTPURLResponse
let statusCode = nsHTTPResponse.statusCode
print ("status code = \(statusCode)")
}
if let error = error {
print ("\(error)")
}
if let data = data {
do{
let jsonResponse = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions())
print ("data = \(jsonResponse)")
}catch _ {
print ("OOps not good JSON formatted response")
}
}
})
task.resume()
}catch _ {
print ("Oops something happened buddy")
}
}
}

Swift: Multithreading Issue Getting Data From API

I'm having a problem where sometimes I can't successfully decode a json file due to a slow networking call. I have a list of stocks and I'm trying to successfully decode each one before the next stock gets decoded.
For example, I would have a list of 4 stocks but 3 would be successful decoded but 1 won't. The one that fails is also random. When I print out the url for the one that fails, the url and json file is correct yet I get an error of it not reading because its on a wrong format.
The list of stocks are retrieved through Firebase and after I receive them, I have a completion handler that tries to make a network call to the server. The reason why I added Firestore code here is because when I put a stop point at when case is successful, I notice that its hitting Thread 5 out of 14 Threads. Is having this many threads common? I know it's a threading issue but am having such a huge problem identifying where I should do dispatchGroups. Any help and clarifications would be much appreciated!
APIManager
private var stocks = [Stock]()
func getStockList( for symbols: [Stock], completion: ((Result<[Stock]>) -> Void)?) {
let dispatchGroup = DispatchGroup()
for symbol in symbols {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = APIManager.baseAPIURL
urlComponents.path = "\(APIManager.baseRelativePath)/market/batch"
//URL parameters
let token = URLQueryItem(name: "token", value: "fakeToken")
let symbolsItem = URLQueryItem(name: "symbols", value: symbol.symbol)
let typesItem = URLQueryItem(name: "types", value: "quote")
urlComponents.queryItems = [token, symbolsItem, typesItem]
guard let url = urlComponents.url else { fatalError("Could not create URL from components") }
var request = URLRequest(url: url)
request.httpMethod = "GET"
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
dispatchGroup.enter()
let task = session.dataTask(with: request) { (responseData, response, err) in
guard err == nil else {
completion?(.failure(err!))
return
}
guard let jsonData = responseData else {
let err = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey : "Data was not retrieved from request"]) as Error
completion?(.failure(err))
return
}
let decoder = JSONDecoder()
do {
let data = try decoder.decode([String: Stock].self, from: jsonData)
let jsonData = data.map{ $0.value }
completion?(.success(jsonData))
dispatchGroup.leave()
} catch {
completion?(.failure(error))
print("Failed to decode using stock URL for \(symbol.symbol ?? ""): \n \(url)")
}
}
task.resume()
}
}
HomeViewController
class HomeViewController: UIViewController, LoginViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
fetchStocksFromFireStore(completion: { (stocks) -> Void in
APIManager.shareInstance.getStockList(for: self.fetchedStocks) { (result) in
switch result {
case .success(let stocks):
stocks.forEach { (stock) in
print("Company: \(stock.companyName ?? "")")
}
case .failure(let error):
print(error.localizedDescription)
}
}
self.tableView.reloadData()
})
}
}
func fetchStocksFromFireStore(completion: #escaping ([Stock]) -> ()) {
let uid = Auth.auth().currentUser?.uid ?? ""
let db = Firestore.firestore()
db.collection("users").document(uid).collection("stocks").getDocuments { (snapshot, err) in
if let err = err {
print("Error getting stocks snapshot", err.localizedDescription)
return
} else {
snapshot?.documents.forEach({ (snapshot) in
var stock = Stock()
stock.symbol = snapshot.documentID
self.fetchedStocks.append(stock)
})
completion(self.fetchedStocks)
}
}
}
Model
struct Stock {
var symbol: String?
var companyName: String?
var latestPrice: Double?
enum CodingKeys: String, CodingKey {
case symbol
case companyName
case latestPrice
}
enum QuoteKeys: String, CodingKey {
case quote
}
}
extension Stock: Encodable {
func encode(to encoder: Encoder) throws {
var quoteContainer = encoder.container(keyedBy: QuoteKeys.self)
var quoteNestedValues = quoteContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .quote)
try quoteNestedValues.encode(symbol, forKey: .symbol)
try quoteNestedValues.encode(companyName, forKey: .companyName)
try quoteNestedValues.encode(latestPrice, forKey: .latestPrice)
}
}
extension Stock: Decodable {
init(from decoder: Decoder) throws {
let quoteValues = try decoder.container(keyedBy: QuoteKeys.self)
let quoteNestedValues = try quoteValues.nestedContainer(keyedBy: CodingKeys.self, forKey: .quote)
symbol = try quoteNestedValues.decode(String.self, forKey: .symbol)
companyName = try quoteNestedValues.decode(String.self, forKey: .companyName)
latestPrice = try quoteNestedValues.decode(Double.self, forKey: .latestPrice)
}
}

Data fetched via postman and URLSession is different in here http://173.249.20.137:9000/apiapp/coupon

http://173.249.20.137:9000/apiapp/coupon GET method .
when I request via URLSession and Postman I get two different results. Actually postman data is correct, but the URL session has always the same response whether I add or delete data it is not going to update. Can anybody please give a look. if it is happening with me only or something wrong at the backend server.
I have tested requesting data with URLSession.shared and postman.
I actually like to have the data I get via postman through URLSession request too.
func getAvailableCoupons(urlString:String, completion: #escaping (_
product: Any, _ error: Error?) -> Void){
guard let url = URL(string: urlString) else {return}
let task = URLSession.shared.dataTask(with: url) { (data, response,
error) in
let jsonDecoder = JSONDecoder()
guard let dataResponse = data,
error == nil else {
print(error?.localizedDescription ?? "Response Error")
return }
let statusCode = (response as! HTTPURLResponse).statusCode
let responseJSON = try? JSONSerialization.jsonObject(with: dataResponse, options: [])
if let responseJSON = responseJSON as? [String: Any] {
if statusCode == 200 {
do {
let jsonData = try JSONSerialization.data(withJSONObject: responseJSON, options: [])
let responseData = try jsonDecoder.decode(CoupensResponseModel.self, from:jsonData)
completion(responseData, nil)
} catch let error {
print("error when parshing json response \(error)")
completion(error, nil )
}
} else if statusCode == 404{
completion(" 404 not found", nil )
} else {
print("fatal error \(error?.localizedDescription ?? "big errror")")
}
}
}
task.resume()
}
import Foundation
// MARK: - CoupensResponseModel
struct CoupensResponseModel: Codable {
let couponDetails: [CouponDetail]?
enum CodingKeys: String, CodingKey {
case couponDetails = "coupon_details"
}
}
// MARK: - CouponDetail
struct CouponDetail: Codable {
let id: Int?
let vouchersusageSet: [VouchersusageSet]?
let couponCode: String?
let minimumSpend: Int?
let expiryDate, createdDate: String?
let discountPrice, discountPercent: Int?
let discountBasis: DiscountBasis?
let couponImage: String?
let couponType: String?
enum CodingKeys: String, CodingKey {
case id
case vouchersusageSet = "vouchersusage_set"
case couponCode = "coupon_code"
case minimumSpend = "minimum_spend"
case expiryDate = "expiry_date"
case createdDate = "created_date"
case discountPrice = "discount_price"
case discountPercent = "discount_percent"
case discountBasis = "discount_basis"
case couponImage = "coupon_image"
case couponType = "coupon_type"
}
}
enum DiscountBasis: String, Codable {
case amount = "amount"
case percent = "percent"
}
// MARK: - VouchersusageSet
struct VouchersusageSet: Codable {
let id: Int?
let itemObj: ItemObj?
let voucherObj: Int?
let sectionObj, categoryObj: Int?
}
// MARK: - ItemObj
struct ItemObj: Codable {
let id: Int?
let code, productName: String?
let productPrice, discountedPrice: Int?
let productDescription, itemImage: String?
let categoryObj: CouponCategoryObj?
enum CodingKeys: String, CodingKey {
case id, code
case productName = "product_name"
case productPrice = "product_price"
case discountedPrice = "discounted_price"
case productDescription = "product_description"
case itemImage = "item_image"
case categoryObj
}
}
// MARK: - CouponCategoryObj
struct CouponCategoryObj: Codable {
let id: Int?
let categoryCode, categoryName: String?
let categoryImage: CouponJSONNull?
let sectionObj: Int?
enum CodingKeys: String, CodingKey {
case id
case categoryCode = "category_code"
case categoryName = "category_name"
case categoryImage = "category_image"
case sectionObj
}
}
// MARK: - Encode/decode helpers
class CouponJSONNull: Codable, Hashable {
public static func == (lhs: CouponJSONNull, rhs: CouponJSONNull) ->
Bool {
return true
}
public var hashValue: Int {
return 0
}
public func hash(into hasher: inout Hasher) {
// No-op
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(CouponJSONNull.self,
DecodingError.Context(codingPath: decoder.codingPath,
debugDescription:
"Wrong type for CouponJSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
Try the following method
let headers = [
"Cache-Control": "no-cache",
]
let request = NSMutableURLRequest(url: NSURL(string: "http://173.249.20.137:9000/apiapp/coupon")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error)
} else {
let string = String(data: data!, encoding: .utf8) ?? ""
print(string)
}
})
dataTask.resume()
Please look on response both are same.
Postman response :
https://jsoneditoronline.org/?id=7b94ef69a3344164aa4a96423fdbf9db
Code response :
https://jsoneditoronline.org/?id=6e5a7d221d9c4c818f1d46fc893031fe

Decoding dynamic JSON structure in swift 4

I have the following issue that I'm not sure how to handle.
My JSON response can look like this:
{
"data": {
"id": 7,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDY1MTU0NDMsImRhdGEiOiJ2bGFkVGVzdCIsImlhdCI6MTU0NjUwODI0M30.uwuPhlnchgBG4E8IvHvK4bB1Yj-TNDgmi7wUAiKmoVo"
},
"error": null
}
Or like this:
{
"data": [{
"id": 12
}, {
"id": 2
}, {
"id": 5
}, {
"id": 7
}],
"error": null
}
So in short the data can be either a single objet or an Array. What i have is this:
struct ApiData: Decodable {
var data: DataObject?
var error: String?
}
struct DataObject: Decodable {
var userId: Int?
enum CodingKeys: String, CodingKey {
case userId = "id"
}
}
This works fine for the first use case, but it will fail once data turns into
var data: [DataObject?]
How do I make that dynamic without duplicating code?
Edit: This is how i decode the object as well
func makeDataTaskWith(with urlRequest: URLRequest, completion: #escaping(_ apiData: ApiData?) -> ()) {
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
session.dataTask(with: urlRequest) {
(data, response, error) in
guard let _ = response, let data = data else {return}
if let responseCode = response as? HTTPURLResponse {
print("Response has status code: \(responseCode.statusCode)")
}
do {
let retreived = try NetworkManager.shared.decoder.decode(ApiData.self, from: data)
completion(retreived)
} catch let decodeError as NSError {
print("Decoder error: \(decodeError.localizedDescription)\n")
return
}
}.resume()
}
If data can be a single object or an array write a custom initializer which decodes first an array, if a type mismatch error occurs decode a single object. data is declared as an array anyway.
As token appears only in a single object the property is declared as optional.
struct ApiData: Decodable {
let data : [DataObject]
let error : String?
private enum CodingKeys : String, CodingKey { case data, error }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
data = try container.decode([DataObject].self, forKey: .data)
} catch DecodingError.typeMismatch {
data = [try container.decode(DataObject.self, forKey: .data)]
}
error = try container.decodeIfPresent(String.self, forKey: .error)
}
}
struct DataObject: Decodable {
let userId : Int
let token : String?
private enum CodingKeys: String, CodingKey { case userId = "id", token }
}
Edit: Your code to receive the data can be improved. You should add a better error handling to return also all possible errors:
func makeDataTaskWith(with urlRequest: URLRequest, completion: #escaping(ApiData?, Error?) -> Void) {
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
session.dataTask(with: urlRequest) {
(data, response, error) in
if let error = error { completion(nil, error); return }
if let responseCode = response as? HTTPURLResponse {
print("Response has status code: \(responseCode.statusCode)")
}
do {
let retreived = try NetworkManager.shared.decoder.decode(ApiData.self, from: data!)
completion(retreived, nil)
} catch {
print("Decoder error: ", error)
completion(nil, error)
}
}.resume()
}
Using power of generic, it simple like below:
struct ApiData<T: Decodable>: Decodable {
var data: T?
var error: String?
}
struct DataObject: Decodable {
private var id: Int?
var userId:Int? {
return id
}
}
Use
if let obj = try? NetworkManager.shared.decoder.decode(ApiData<DataObject>.self, from: data) {
//Do somthing
} else if let array = try NetworkManager.shared.decoder.decode(ApiData<[DataObject]>.self, from: data) {
// Do somthing
}
If you have only two possible outcomes for your data, an option would be to try and parse data to one of the expected types, if that fails you know that the data is of other type and you can then handle it accordingly.
See this
You can try
struct Root: Codable {
let data: DataUnion
let error: String?
}
enum DataUnion: Codable {
case dataClass(DataClass)
case datumArray([Datum])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode([Datum].self) {
self = .datumArray(x)
return
}
if let x = try? container.decode(DataClass.self) {
self = .dataClass(x)
return
}
throw DecodingError.typeMismatch(DataUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for DataUnion"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .dataClass(let x):
try container.encode(x)
case .datumArray(let x):
try container.encode(x)
}
}
}
struct Datum: Codable {
let id: Int
}
struct DataClass: Codable {
let id: Int
let token: String
}
let res = try? JSONDecoder().decode(Root.self, from:data)

Swift 4 Codable - API provides sometimes an Int sometimes a String

I have Codables running now. But the API has some String entries that can sometimes have an Int value of 0 if they are empty. I was searching here and found this: Swift 4 Codable - Bool or String values But I'm not able to get it running
My struct
struct check : Codable {
let test : Int
let rating : String?
}
Rating is most of the time something like "1Star". But if there is no rating I get 0 as Int back.
I'm parsing the data like this:
enum Result<Value> {
case success(Value)
case failure(Error)
}
func checkStar(for userId: Int, completion: ((Result<check>) -> Void)?) {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "xyz.com"
urlComponents.path = "/api/stars"
let userIdItem = URLQueryItem(name: "userId", value: "\(userId)")
urlComponents.queryItems = [userIdItem]
guard let url = urlComponents.url else { fatalError("Could not create URL from components") }
var request = URLRequest(url: url)
request.httpMethod = "GET"
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = [
"Authorization": "Bearer \(keytoken)"
]
let session = URLSession(configuration: config)
let task = session.dataTask(with: request) { (responseData, response, responseError) in
DispatchQueue.main.async {
if let error = responseError {
completion?(.failure(error))
} else if let jsonData = responseData {
// Now we have jsonData, Data representation of the JSON returned to us
// from our URLRequest...
// Create an instance of JSONDecoder to decode the JSON data to our
// Codable struct
let decoder = JSONDecoder()
do {
// We would use Post.self for JSON representing a single Post
// object, and [Post].self for JSON representing an array of
// Post objects
let posts = try decoder.decode(check.self, from: jsonData)
completion?(.success(posts))
} catch {
completion?(.failure(error))
}
} else {
let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey : "Data was not retrieved from request"]) as Error
completion?(.failure(error))
}
}
}
task.resume()
}
Loading it:
func loadStars() {
checkStar(for: 1) { (result) in
switch result {
case .success(let goo):
dump(goo)
case .failure(let error):
fatalError(error.localizedDescription)
}
}
}
I hope someone can help me there, cause I'm not completely sure how this parsing, etc. works.
you may implement your own decode init method, get each class property from decode container, during this section, make your logic dealing with wether "rating" is an Int or String, sign all required class properties at last.
here is a simple demo i made:
class Demo: Decodable {
var test = 0
var rating: String?
enum CodingKeys: String, CodingKey {
case test
case rating
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let test = try container.decode(Int.self, forKey: .test)
let ratingString = try? container.decode(String.self, forKey: .rating)
let ratingInt = try? container.decode(Int.self, forKey: .rating)
self.rating = ratingString ?? (ratingInt == 0 ? "rating is nil or 0" : "rating is integer but not 0")
self.test = test
}
}
let jsonDecoder = JSONDecoder()
let result = try! jsonDecoder.decode(Demo.self, from: YOUR-JSON-DATA)
if rating API's value is normal string, you will get it as you wish.
if rating API's value is 0, rating will equal to "rating is nil or 0"
if rating API's value is other integers, rating will be "rating is integer but not 0"
you may modify decoded "rating" result, that should be easy.
hope this could give you a little help. :)
for more info: Apple's encoding and decoding doc