How to ensure firebase data is always an array? - swift

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

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

Firebase Data Swift

I've got a little problem gaining objects from data base.It contains 4 objects(4 images specifically), they're coming but for some reason they overlap. If I print out query item, the logo shows 4 references to the images which is completely correct but when start setting data up to the image, instead of getting 4 images, I get 16. Is there any way to fix? I though it could be fixed by changing the model from struct to class to avoid duplicating but it didn't work out. Maybe a triple loop causes the problem.
class APIManager {
private init() {}
static let shared = APIManager()
func fetchData(collectionType: CollectionType, completion: #escaping (Document) -> Void) {
let dataBase = self.configureFireBase()
dataBase.collection(collectionType.rawValue).getDocuments { (querySnapshot, error) in
if let error = error {
print("Error received trying to get data: \(error)")
}else{
guard let query = querySnapshot?.documents else {return}
for queryItem in query {
for(name, price) in queryItem.data() {
if name == "field1" {
self.getImage { (sneakerImage) in
let documentToAppend = Document(name: queryItem.documentID, field1: price as? String ?? "", field2: queryItem.documentID, image: sneakerImage)
completion(documentToAppend)
}
}
}
}
}
}
}
private func getImage(imageHandler: #escaping (UIImage)->Void) {
let storage = Storage.storage()
let reference = storage.reference(forURL: "gs://sneakershop-b309a.appspot.com").child("pictures")
reference.listAll { (result, error) in
for item in result.items {
print(item)
item.getData(maxSize: 1024 * 1024) { (data, error) in
if let error = error {
print("error: \(error.localizedDescription)")
}else{
guard let image = UIImage(data: data ?? Data()) else {return}
imageHandler(image)
}
}
}
}
}
private func configureFireBase() -> Firestore {
var db: Firestore!
let settings = FirestoreSettings()
Firestore.firestore().settings = settings
db = Firestore.firestore()
return db
}
}

API Exclude data if date is in the past

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.

Swift function produces a "SIGILL" on it's return statement

I am writing a piece of code in Swift to hit a public API endpoint to pull back data in JSON and use it in the application. I am using URLSession to do the request and am using an async/await similar paradigm to extract data out of the URLSession callback and place it in a local variable. Then, the function returns the optional dictionary returned by JSONSerialization to the caller.
This code executes perfectly fine outside of a function and run as part of the main program, but as soon as it is moved to a function, the return statement produces a "SIGILL" exit.
I breakpointed to the return statement and found that it is exactly what is throwing this error. Since this is an optional dictionary, I tried just returning an unwrapped version of the dictionary and found the same results. I also tried just returning a blank dictionary and I still get a SIGILL
Functioning:
let url = URL(string: <endpointURL>)!
var tenant: [String: Any]? = nil;
let sem = DispatchSemaphore(value: 1)
sem.wait()
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
if let json = json {
print(json)
tenant = json
sem.signal()
} else {
print("ERR: Null JSON")
}
} catch let error as NSError {
print("ERR: " + error.localizedDescription)
}
} else if let error = error {
print("ERR: " + error.localizedDescription);
} else {
print("ERR: Unknown")
}
}
print("resuming")
task.resume()
print("waiting: ")
sem.wait()
print("done waiting")
print(tenant!["tenant_name"]!)
Fails:
let _ = HttpHelper.getTenantFor(tenantId: <someUUID>)
class HttpHelper {
static func getTenantFor(tenantId: String) -> [String:Any]? {
let url = URL(string: <endpointURL>)!
var tenant: [String: Any]? = nil;
let sem = DispatchSemaphore(value: 1)
sem.wait()
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
if let json = json {
print(json)
tenant = json
sem.signal()
} else {
print("ERR: Null JSON")
}
} catch let error as NSError {
print("ERR: " + error.localizedDescription)
}
} else if let error = error {
print("ERR: " + error.localizedDescription);
} else {
print("ERR: Unknown")
}
}
print("resuming")
task.resume()
print("waiting: ")
sem.wait()
print("done waiting")
return [String:Any]()
}
}
On the functioning code, the app outputs the proper value for the "tenant_name" key in the JSON object and in the failed code I get the following:
Process finished with exit code 132 (interrupted by signal 4: SIGILL)