API Exclude data if date is in the past - swift

I am trying to exclude data from my API response if the nested list 'calls' contains past properties
Include this data in response:
[
{
"addressLineOne":"Test",
"addressLineTwo":"Test2",
"calls":{
"dateTime":1597932000, // a date in the future
},
]
Exclude this data:
[
{
"addressLineOne":"Test",
"addressLineTwo":"Test2",
"calls":{
"dateTime":1596193200 // a date in the past
},
]
I am using JSON decoder to make my api calls:
class Service {
static let shared = Service()
let BASE_URL = "url.com/JsonData"
func fetchClient(completion: #escaping ([Client]) -> ()) {
guard let url = URL(string: BASE_URL) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
// handle error
if let error = error {
print("Failed to fetch data with error: ", error.localizedDescription)
return
}
guard let data = data else {return}
do {
let clients = try JSONDecoder().decode([Client].self, from: data)
completion(clients)
} catch let error {
print("Failed to create JSON with error: ", error.localizedDescription)
}
}.resume()
}
}
Any direction would be much appreciated

Managed to solve it by adding a filter and using the built-in Calendar function to check the date:
class Service {
static let shared = Service()
let BASE_URL = "url.com/JsonData"
let calendar = Calendar.current
func fetchClient(completion: #escaping ([Client]) -> ()) {
guard let url = URL(string: BASE_URL) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
// handle error
if let error = error {
print("Failed to fetch data with error: ", error.localizedDescription)
return
}
guard let data = data else {return}
do {
let myDecoder = JSONDecoder()
myDecoder.dateDecodingStrategy = .secondsSince1970 // formats date
let clients = try myDecoder.decode([Client].self, from: data)
completion(clients.filter { self.calendar.isDateInToday($0.calls.dateTime) // filters dates upon completion
})
} catch let error {
print("Failed to create JSON with error: ", error.localizedDescription)
}
}.resume()
}
}
In my solution the API call completes before filtration, which is less-than-ideal as it means all data is downloaded prior to filtering, ideally i'd like the data to be filtered prior to download anyone who can point me in the right direction on achieving this is welcomed.
Also this solution only checks if the date is today, not if the date is in the future.

Related

Publishing changes from background threads is not allowed; make sure to publish values from the main thread

I'm having an issue with a Swift function. I have my class which manages all the networking, and in there I have two functions: fetchData, which gets an array of IDs, and getPosts, which gets all posts related to those IDs via a DispatchGroup and appends them to a published array, which I will then use in the View.
The issue I'm facing is:
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates..
The published variable assignment is already wrapped in a DispatchQueue.main.async block. What am I missing?
import Foundation
class NetworkManager: ObservableObject {
var feedPosts: [Int] = []
#Published var posts = [Post]()
func fetchData(feedType: PostsOrderType) -> Void {
if let url = URL(string: "https://hacker-news.firebaseio.com/v0/\(feedType)stories.json") {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if (error == nil){
let decoder = JSONDecoder()
if let result = data {
do {
let decodedRes = try decoder.decode([Int].self, from: result)
self.feedPosts = decodedRes
self.getPosts()
} catch {
print("Decoding error: \(error)")
}
}
} else {
print("Error retrieving data: \(error!)")
}
}
task.resume()
}
}
func getPosts() -> Void {
let dGroup = DispatchGroup()
//Limited to 15 to reduce networking times
let firstTenResults = self.feedPosts[0...15]
for id in firstTenResults{
guard let url = URL(string: "https://hacker-news.firebaseio.com/v0/item/\(id).json")
else {
return
}
dGroup.enter()
URLSession.shared.dataTask(with: url){
data, response, error in
let decoder = JSONDecoder()
if let result = data {
do {
let decodedRes = try decoder.decode(Post.self, from: result)
DispatchQueue.main.async {
self.posts.append(decodedRes)
}
} catch {
print("Error decoding data: \(error)")
}
}
dGroup.leave()
}.resume()
}
}
}

In SwiftUI, how can return a function only when an API request is finished? [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 1 year ago.
I'm trying to learn a bit about making API calls in SwiftUI.
I have a function called loadData which runs via the onAppear modifier.
The goal of that function is to see if I currently have data in CoreData.
If there is no data in CoreData, then I'd like to call another function that makes the API call to get the data, but only return the fetched data.
With the example I have below, the getCurrentSol function returns before the async portion is finished. Resulting in no data being returned. What is the appropriate way for me to return the data?
As you can see, I did try a while(true) "trick". But for whatever reason, my results variable never even updates with the fetched data, even though the decodedData variable does contain the proper results.
}.onAppear(perform: loadData)
}
func loadData() {
print("data: \(storedData) ")
print("data.count: \(storedData.count)")
if(storedData.count == 0){
let fetchedData = getCurrentSol()
let currentSol = fetchedData.sol
print("fetchedData: \(fetchedData)")
print("currentSol: \(currentSol)")
}
}
func getCurrentSol() -> CuriosityRoverModel {
var results = CuriosityRoverModel(sol: 0, low: 0, high: 0, opacity: "Sunny", sunrise: "00:00", sunset: "00:00", month: "Month 0")
let urlString = "https://api.maas2.apollorion.com"
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) {data, response, error in
DispatchQueue.main.async {
if let data = data {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(CuriosityRoverModel.self, from: data)
//This recieves the proper data, but it doesn't get written to the results var
print("decodedData: \(decodedData)")
results = decodedData
} catch {
print("Error: ", error)
}
}
}
}.resume()
// I thought this would be a way to wait for the data
// but results never gets updated so it ends up running endlessly
while(true){
if(results.sol > 0){
return results
}
}
//return results // This would just return the "empty" results var from above before the data is actually retrieved
}
}
There are many ways to achieve what you want. This is one approach, using a closure:
....
.onAppear(perform: loadData)
}
func loadData() {
print("data: \(storedData) ")
print("data.count: \(storedData.count)")
if (storedData.count == 0) {
getCurrentSol() { results in // <--- here
if let fetchedData = results {
let currentSol = fetchedData.sol
print("fetchedData: \(fetchedData)")
print("currentSol: \(currentSol)")
}
}
}
}
// use a completion closure to "return" your results when done, not before
func getCurrentSol(completion: #escaping (CuriosityRoverModel?) -> Void) {
let urlString = "https://api.maas2.apollorion.com"
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) {data, response, error in
DispatchQueue.main.async {
if let data = data {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(CuriosityRoverModel.self, from: data)
print("decodedData: \(decodedData)")
completion(decodedData) // <--- here, return the results
} catch {
print("Error: ", error) // need to deal with errors
completion(nil) // <--- here, should return the error
}
}
}
}.resume()
}
}

How to ensure firebase data is always an array?

I have a firebase realtime database that is an array, however when certain nodes are deleted, the data goes from array to dictionary.
Example:
Before:
[
{
"callDescription":"Call Zero",
"callDuration":"call Zero",
"callID":0,
},
{
"callDescription":"ssadsad",
"callDuration":"5-8 Minutes",
"callID":1,
},
{
"callDescription":"dfdsfdsfsd",
"callDuration":"5-8 Minutes",
"callID":2,
}
]
Then the callID: 0 & callID: 1 nodes are deleted and it turns into:
{
"2":{
"callDescription":"dfdsfdsfsd",
"callDuration":"5-8 Minutes",
"callID":2,
}
}
How can I ensure that the data remains an array regardless of nodes being deleted?
I am using swift, and my app expects an array, so any time a dict is received I get an error.
Here is how I am decoding:
class Service {
static let shared = Service()
let BASE_URL = "https://url.com"
func fetchClient(completion: #escaping ([Calls]) -> ()) {
guard let url = URL(string: BASE_URL) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
// handle error
if let error = error {
print("Failed to fetch data with error: ", error)
return
}
guard let data = data else {return}
do {
let myDecoder = JSONDecoder()
let calls = try myDecoder.decode([Calls].self, from: data)
completion(calls)
} catch let error {
print("Failed to create JSON with error: ", error)
}
}.resume()
}

Better alternative to DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) for data refresh

I am looking for a better alternative to DispatchQueue.main.asyncAfter(deadline: .now() + 0.5 to refresh my data.
My data is dynamic and changes based on user action, it's important that the data the user sees is always up to date.
I am using Firebase Realtime Database, I am wondering whether I can alter my service file to refetch data any time something changes. here is my service file:
class Service {
static let shared = Service()
let BASE_URL = "https://firebaseurl.com/jsondata.json"
let calendar = Calendar.current
func fetchClient(completion: #escaping ([Calls]) -> ()) {
guard let url = URL(string: BASE_URL) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
// handle error
if let error = error {
print("Failed to fetch data with error: ", error)
return
}
guard let data = data else {return}
do {
let myDecoder = JSONDecoder()
myDecoder.dateDecodingStrategy = .secondsSince1970
let calls = try myDecoder.decode([Calls?].self, from: data).filter({
self.calendar.isDateInToday($0?.dateTime ?? Date(timeIntervalSinceReferenceDate: -123456789.0))
})
completion(calls.filter{$0?.callmade != true}.compactMap{ $0 })
} catch let error {
print("Failed to create JSON with error: ", error)
}
}.resume()
}
}
Currently in my Main Controller I am using:
func fetchClient() {
Service.shared.fetchClient { (client) in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.client = client
self.collectionView.reloadData()
self.fetchClient()
}
}
}
You can use Realtime Database to observer changes in data.
https://firebase.google.com/docs/database/ios/read-and-write#listen_for_value_events

Swift after json parsing variables are assigned to their initial values

I'm new to swift i am sorry if this is a stupid question
I am trying to expand my knowledge in macOS development and i am trying out new things
i am parsing a json file from an url
it works fine in the do{}catch{} brackets however, i want to use what i get from the json data in other parts of the program.
i created some variables to store the values.
However, they go back to their initial value once the do{}catch{} execution is done
how can i store the values I got
#IBAction func buttonPressed(_ sender: Any) {
var summonerNameGlobal: String = ""
var summonerIdGlobal: String = ""
var summonerPuuidGlobal: String = ""
var summonerAccountIdGlobal: String = ""
let jsonString = "https://na1.api.riotgames.com/lol/summoner/v4/summoners/by-name/john?api_key=\(apiKey)"
guard let url = URL(string: jsonString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
DispatchQueue.main.async {
do {
let summoner = try JSONDecoder().decode(SummonerInfo.self, from: data)
self.summonerIdLabel.stringValue = summoner.id
summonerNameGlobal = summoner.name
summonerIdGlobal = summoner.id
summonerAccountIdGlobal = summoner.accountId
summonerPuuidGlobal = summoner.puuid
} catch {
print(error)
}
}
}.resume()
print(summonerNameGlobal)
print(summonerPuuidGlobal)
print(summonerIdGlobal)
print(summonerAccountIdGlobal)
}
They are not going to default again but you are checking them before they are being set ... because async function take some time to get response from server but your print statements run immediately
What you can do is to check values once they are set
func callApi(completion: #escaping (SummonerInfo?)->Void){
let jsonString = "https://na1.api.riotgames.com/lol/summoner/v4/summoners/by-name/john?api_key=\(apiKey)"
guard let url = URL(string: jsonString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
DispatchQueue.main.async {
do {
let summoner = try JSONDecoder().decode(SummonerInfo.self, from: data)
completion(summoner)
} catch {
completion(nil)
print(error)
}
}
}.resume()
}
#IBAction func buttonPressed(_ sender: Any) {
callApi { [weak self] info in
if let getInfo = info {
print(getInfo.name)
print(getInfo.id)
print(getInfo.accountId)
print(getInfo.puuid)
} else {
print("data is nil")
}
}
}