I am having an issue where I run this function:
static func getAllJokes(reset: Bool, completion: #escaping () -> Void) {
Utilities.getDadJokes(reset: reset) {
print("Dad Jokes Pass, array = \(DadJokes.dadJokes)")
Utilities.getAssistantJokes(reset: reset) {
print("Assistant Jokes Pass, array = \(AssistantJokes.assistantJokes)")
Utilities.getKnockKnockJokes(reset: reset) {
print("Knock Knock Jokes Pass, array = \(KnockKnockJokes.knockKnockJokes)")
Utilities.getRandomJokes(reset: reset) {
print("Random Jokes Pass, array = \(RandomJokes.randomJokes)")
completion()
}
}
}
}
}
getDadJokes:
db.collection("jokes").document("Dad Jokes").addSnapshotListener { document, error in
//check for error
if error == nil {
//check if document exists
if document != nil && document!.exists {
if let JokeNum = document!.get("JokeNum") as? Int {
self.countDadJokes = JokeNum
UserDefaults.standard.setValue(JokeNum, forKey: "countDadJokes")
print("DadJokeNum = \(self.countDadJokes)")
}
// var DadJokes.dadJokes.count = 1
print("count = \(DadJokes.dadJokes.count)/\(self.countDadJokes)")
print("countDadJoke = \(self.countDadJokes)")
self.jokes.removeAll()
if reset == true {
DadJokes.dadJokes.removeAll()
}
// for _ in 0...self.countDadJokes {
while DadJokes.dadJokes.count <= self.countDadJokes {
// print("count = \(DadJokes.dadJokes.count)/\(self.countDadJokes)")
if let Joke = document!.get("\(DadJokes.dadJokes.count + 1)") as? String {
print("DadJokeNum = \(self.countDadJokes)")
if Utilities.jokes.contains("\(Joke) - From Dad Jokes") {}else {
Utilities.jokes.append("\(Joke) - From Dad Jokes")
DadJokes.dadJokes.append(Joke)
UserDefaults.standard.set(DadJokes.dadJokes, forKey: defaults.dadJokes.rawValue)
Utilities.updateJokesDefaults()
print("countDadJokesSaved = \(DadJokes.dadJokes.count)")
print("DadJokesSaved = \(DadJokes.dadJokes)")
}
print("Dad Joke: \(Joke)")
//print("count = \(DadJokes.dadJokes.count)/\(self.countDadJokes)")
if DadJokes.dadJokes.count == self.countDadJokes {
completion()
}
}
}
}
}
}
runs fine, but then when I run getAssistantJoke (the same thing):
print("assistant get running")
db.collection("jokes").document("Assistant Jokes").addSnapshotListener { document, error in
//check for error
if error == nil {
//check if document exists
if document != nil && document!.exists {
if let JokeNum = document!.get("JokeNum") as? Int {
self.countAssistantJokes = JokeNum
UserDefaults.standard.setValue(JokeNum, forKey: defaults.countAssistantJokes.rawValue)
print("DadJokeNum = \(self.countDadJokes)")
}
// var DadJokes.dadJokes.count = 1
print("count = \(AssistantJokes.assistantJokes.count)/\(self.countAssistantJokes)")
print("countAssistantJokes = \(self.countAssistantJokes)")
if reset == true {
AssistantJokes.assistantJokes.removeAll()
}
// for _ in 0...self.countDadJokes {
while AssistantJokes.assistantJokes.count <= self.countAssistantJokes {
// print("count = \(AssistantJokes.assistantJokes.count)/\(self.countAssistantJokes)")
if let Joke = document!.get("\(DadJokes.dadJokes.count + 1)") as? String {
print("AssistantJokeNum = \(self.countAssistantJokes)")
if Utilities.jokes.contains("\(Joke) - From Assistant Jokes") {}else {
Utilities.jokes.append("\(Joke) - From Assistant Jokes")
AssistantJokes.assistantJokes.append(Joke)
UserDefaults.standard.set(AssistantJokes.assistantJokes, forKey: defaults.assistantJokes.rawValue)
Utilities.updateJokesDefaults()
print("countAssistantJokesSaved = \(AssistantJokes.assistantJokes.count)")
print("AssistantJokesSaved = \(AssistantJokes.assistantJokes)")
}
print("Assistant Joke: \(Joke)")
// print("count = \(AssistantJokes.assistantJokes.count)/\(self.countAssistantJokes)")
if AssistantJokes.assistantJokes.count == self.countAssistantJokes {
completion()
}
}
}
}
}
}
}
It stops at firebase query. The documents and everything exist, therefor, what's the issue? Everything exists and I am truly stumped. My goal is to retrieve a ton of data and display a random subject. I feel like this should be easier than it is.
Here is my firebase setup:
I feel like the main problem here is the way in which you are modelling the data in Firestore.
You have a collection called jokes but then a document called Assistant Jokes, Dad Jokes, etc...
I think a much better structure would be for each document to be a single joke inside the jokes collection. Use an "auto id" for each joke and then put a type inside the joke document.
The fact that you have to maintain a JokeNum next to your list of jokes is a sign that something is wrong.
By structuring this way you would then be able to get all jokes or filter the jokes by a particular type etc...
Your code is super complex at the moment but I think the best approach is to structure your data in a way that helps your code.
Related
The following is a function to add a listener to a query. Whenever a document is added/removed I make some changes on two arrays (one of the user Ids and one of the user details). As you can see I tried printing everything: I correctly receive the data whenever it is added/removed, I can retrieve the document ID I need but whenever I append it to the usersReqestedUIDs array it always prints it as empty, even if I try to append a random string in it. Why is that?
func addRequestedUsersSnapshot() {
let db = Firestore.firestore()
let userRef = db.collection("user").document(user.UID)
let userRequestedRef = userRef.collection("friends").whereField("status", isEqualTo: "request")
// First query to fetch all friendIDs
userRequestedRef.addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: (error!)")
return
}
snapshot.documentChanges.forEach { diff in
print("ids : (self.usersReqestedUIDs)")
print("type : (diff.type)")
if diff.type == .added {
print("doc id (diff.document.documentID)")
self.usersReqestedUIDs.append("hello")
print("added (diff.document.data())")
print("ids : (self.usersReqestedUIDs)")
self.fetchUserDetailsByUID(uid: diff.document.documentID) { result in
switch result {
case let .success(user):
self.usersReqestedDetails.append(user)
case let .failure(error):
print(error)
}
}
}
if diff.type == .removed {
print("removed (diff.document.data())")
self.usersReqestedDetails.removeAll(where: { $0.UID == diff.document.documentID })
self.usersReqestedUIDs.removeAll(where: { $0 == diff.document.documentID })
}
if diff.type == .modified {
print("modified (diff.document.data())")
}
}
}
}
I am transitioning from UIKit to SwiftUI in my app and am updating network calls to async await as well. I created a Large size widget that displays the weather for 7 airports. The airports identifiers are stored in an app group shared userdefatults container. I update the timeline every minute (just for testing, normally it would be every 20 minutes). Initially when the widget is selected and appears, all data is there and correct. After a timeline update the data updates, but not all the airports are returned and after two updates or so (not consistent), the screen goes blank. The userdefaults airports are updated from the main app and saved in the shared user defaults container and it calls WidgetCenter.shared.reloadAllTimelines. This is all working fine as I have another process that uses the same container for a small widget, but with only one airport returning data without the need for an appended array. If I remove the calls to empty the array, the data remains and doesn't go blank, but of course the array keeps appending. I've tried the removeAll() and [] to empty the array at different places in the code, but same result. I am trying to understand the flow in the async/await calls, but seem to be missing something here? Any help would be greatly appreciated. I've been googling and searching stack overflow for a month and don't really know how to solve this issue. Thanks in advance!
actor MetarService: NSObject, XMLParserDelegate, URLSessionDelegate, ObservableObject {
enum MetarFetcherError: Error {
case invalidServerResponse
case missingData
}
#Published var metarArray = [String]()
#Published var metarDataModel: [MetarDataModel] = []
var tempDataModel: [MetarDataModel] = []
func fetchMetars(metarAPTs: String) async throws -> [MetarDataModel] {
let wxUrl = URL(string: "https://www.aviationweather.gov/adds/dataserver_current/httpparam?dataSource=metars&requestType=retrieve&format=xml&hoursBeforeNow=3&mostRecent=true&stationString=" + metarAPTs)!
let (data, response) = try await URLSession.shared.data(from: wxUrl)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw MetarFetcherError.invalidServerResponse
}
guard let xml = SWXMLHash.parse(data) as XMLIndexer? else {
throw MetarFetcherError.missingData
}
noDataResponse = (xml["response"]["data"].element?.attribute(by: "num_results")?.text) ?? "0"
if noDataResponse == "1" && (xml["response"]["data"]["METAR"]["observation_time"].element?.text) != nil {
if (xml["response"]["data"]["METAR"]["station_id"].element?.text) != nil {
myairport = xml["response"]["data"]["METAR"]["station_id"].element!.text
} else {
myairport = "MSNG"
}
if (xml["response"]["data"]["METAR"]["flight_category"].element?.text) != nil {
myfltcat = xml["response"]["data"]["METAR"]["flight_category"].element!.text
} else {
myfltcat = "MISNG"
}
switch myfltcat {
case "VFR":
mymetarImage = "sun.max.circle.fill"
case "MVFR":
mymetarImage = "cloud.sun.circle.fill"
case "IFR":
mymetarImage = "cloud.fog.circle.fill"
case "LIFR":
mymetarImage = "smoke.circle.fill"
default:
mymetarImage = "person.crop.circle.badge.questionmark"
}
if (xml["response"]["data"]["METAR"]["observation_time"].element?.text) != nil {
myobstime = xml["response"]["data"]["METAR"]["observation_time"].element!.text as NSString
if myobstime.length < 16 {
myobstime = "MISNG"
} else {
myobstime = myobstime.substring(with: NSRange(location: 11, length: 5)) as NSString
}
}
if (xml["response"]["data"]["METAR"]["visibility_statute_mi"].element?.text) != nil {
myvis = xml["response"]["data"]["METAR"]["visibility_statute_mi"].element!.text
let intVis = (myvis as NSString) .integerValue
myvis = String(intVis) + "SM"
} else {
myvis = "0"
}
if (xml["response"]["data"]["METAR"]["wind_dir_degrees"].element?.text) != nil {
mywinddir = xml["response"]["data"]["METAR"]["wind_dir_degrees"].element!.text
if mywinddir.contains("VRB") {
mywinddir = "VRB"
} else
if mywinddir.count <= 2 && mywinddir.count > 0 {
mywinddir = "0" + mywinddir
}
} else {
mywinddir = "MISNG"
}
if (xml["response"]["data"]["METAR"]["wind_speed_kt"].element?.text) != nil {
mywindspd = xml["response"]["data"]["METAR"]["wind_speed_kt"].element!.text
if mywindspd == "0" {
mywind = "Calm"
} else if mywindspd.count == 1 {
mywindspd = "0" + mywindspd
mywind = mywinddir + "/" + mywindspd + "KT"
} else if mywindspd.count > 1 {
mywind = mywinddir + "/" + mywindspd + "KT"
}
} else {
mywind = "MISNG"
}
}
self.tempDataModel.append(MetarDataModel(metarImage: mymetarImage, mairport: myairport, mobstime: myobstime as String, mfltcat: myfltcat, mvis: myvis, mwind: mywind))
self.metarDataModel = self.tempDataModel
tempDataModel = []
return metarDataModel
}
func readMetarApts() -> [String] {
let defaults = UserDefaults(suiteName: "group.userdefaults.shared.FRAT")!
if ((defaults.value(forKey: "icaoIdent") as! String).isEmpty) {
defaultairport = "KSFO"
} else {
defaultairport = defaults.value(forKey: "icaoIdent") as! String
}
wxAirport1 = defaults.value(forKey: "wxAirport1") as! String
wxAirport2 = defaults.value(forKey: "wxAirport2") as! String
wxAirport3 = defaults.value(forKey: "wxAirport3") as! String
wxAirport4 = defaults.value(forKey: "wxAirport4") as! String
wxAirport5 = defaults.value(forKey: "wxAirport5") as! String
wxAirport6 = defaults.value(forKey: "wxAirport6") as! String
metarArray.append(defaultairport)
metarArray.append(wxAirport1)
metarArray.append(wxAirport2)
metarArray.append(wxAirport3)
metarArray.append(wxAirport4)
metarArray.append(wxAirport5)
metarArray.append(wxAirport6)
metarArray = metarArray.sorted()
let returnArray = metarArray
metarArray = []
return returnArray
}// end of readAirports function
nonisolated func getAirports() -> ([MetarDataModel] ){
// transData = []
Task{
let tempArray = await readMetarApts()
for apts in tempArray {
let zData = try await self.fetchMetars(metarAPTs: apts)
if zData .isEmpty {
let errorData = MetarDataModel(metarImage: "sun.max.circle.fill", mairport: "DATA", mobstime: "CHK", mfltcat: "MSNG", mvis: "WiFi", mwind: "")
tempData = [errorData]
} else {
transData.append(contentsOf: zData)
tempData = transData
} // else Closure
} //Task Closure
//transData.removeAll()
} // apts in tempArray Closure
tempData = transData
// transData = []
return tempData.sorted()
} // end of getAirports function
} // end of MetarService Class
I have tried different solutions found on stack overflow, reddit, medium and others. But no matter what approach I take, if I try and empty the appended array in preparation for the next timeline update, it will always eventually return without data. At first I thought it was the app group shared container losing reference as I got the much debated 'kCFPreferencesAnyUser, ByHost: Yes, Container: (null)): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd' in the log, but Apple says this does not indicate that particular condition? And, I use the container elsewhere with no issues. I am new to the async await and the new URLSession.shared.data(from: ) and maybe I'm not understanding the flow and timing of how the data is fetched and returned? I just need to append the array, display the data, then empty the array and wait for the next timeline to fetch new data. I've put the removeAll() and tried [] as an alternative in many different places in my code (at the start of the function and at the end of the function). Stumped!!
I have an array of Property<Int>, and I need to reduce them to get sum of the last inputs (it's basically an unread notification counter from different SDK's) and put that into new Property<Int>, I tried this
let unseen: Property<Int> = .init(
initial: 0,
then: countExtractor(counters: counters)
)
func countExtractor(counters: [Property<Int>]) -> SignalProducer<Int?, Never> {
SignalProducer { observer, lifetime in
guard !lifetime.hasEnded else {
return
}
let producers = counters.map { $0.producer }
lifetime += SignalProducer<SignalProducer<Int, Never>, Never>(producers)
.flatten(.latest)
.reduce(0) { previous, current in
return (previous ?? 0) + current
}
.start(observer)
}
}
And it's working, but the values are not updating (when I hard code only one property everything is working correctly)
The answer was so simple
let properties = Property<Int>.combineLatest(counters)
let count = properties?.producer.map {
$0.reduce(0, +)
}
guard let count = count else {
return SignalProducer(value: 0)
}
return count
Here is my method to retrieve an array of user and post objects from the database.
func getRecentPost(start timestamp: Int? = nil, limit: UInt, completionHandler: #escaping ([(Post, UserObject)]) -> Void) {
var feedQuery = REF_POSTS.queryOrdered(byChild: "timestamp")
if let latestPostTimestamp = timestamp, latestPostTimestamp > 0 {
feedQuery = feedQuery.queryStarting(atValue: latestPostTimestamp + 1, childKey: "timestamp").queryLimited(toLast: limit)
} else {
feedQuery = feedQuery.queryLimited(toLast: limit)
}
// Call Firebase API to retrieve the latest records
feedQuery.observeSingleEvent(of: .value, with: { (snapshot) in
let items = snapshot.children.allObjects
let myGroup = DispatchGroup()
var results: [(post: Post, user: UserObject)] = []
for (index, item) in (items as! [DataSnapshot]).enumerated() {
myGroup.enter()
Api.Post.observePost(withId: item.key, completion: { (post) in
Api.User.observeUser(withId: post.uid!, completion: { (user) in
results.insert((post, user), at: index) //here is where I get my error -> Array index is out of range
myGroup.leave()
})
})
}
myGroup.notify(queue: .main) {
results.sort(by: {$0.0.timestamp! > $1.0.timestamp! })
completionHandler(results)
}
})
}
Here is the call to the method from my view controller. I am currently using texture UI to help with a faster smoother UI.
var firstFetch = true
func fetchNewBatchWithContext(_ context: ASBatchContext?) {
if firstFetch {
firstFetch = false
isLoadingPost = true
print("Begin First Fetch")
Api.Post.getRecentPost(start: posts.first?.timestamp, limit: 8 ) { (results) in
if results.count > 0 {
results.forEach({ (result) in
posts.append(result.0)
users.append(result.1)
})
}
self.addRowsIntoTableNode(newPhotoCount: results.count)
print("First Batch Fetched")
context?.completeBatchFetching(true)
isLoadingPost = false
print("First Batch", isLoadingPost)
}
} else {
guard !isLoadingPost else {
context?.completeBatchFetching(true)
return
}
isLoadingPost = true
guard let lastPostTimestamp = posts.last?.timestamp else {
isLoadingPost = false
return
}
Api.Post.getOldPost(start: lastPostTimestamp, limit: 9) { (results) in
if results.count == 0 {
return
}
for result in results {
posts.append(result.0)
users.append(result.1)
}
self.addRowsIntoTableNode(newPhotoCount: results.count)
context?.completeBatchFetching(true)
isLoadingPost = false
print("Next Batch", isLoadingPost)
}
}
}
In the first section of code, I have debugged to see if I could figure out what is happening. Currently, firebase is returning the correct number of objects that I have limited my query to (8). But, where I have highlighted the error occurring at, the index jumps when it is about to insert the fifth object, index[3] -> 4th object is in array, to index[7]-> 5th object about to be parsed and inserted, when parsing the 5th object.
So instead of going from index[3] to index[4] it jumps to index[7].
Can someone help me understand what is happening and how to fix it?
The for loop has continued on its thread while the observeUser & observePost callbacks are on other threads. Looking at your code, you can probably just get away with appending the object to the results array instead of inserting. This makes sense because you are sorting after the for loop anyway, so why does the order matter?
How would i get in correct way last record from the data base ? For now I am getting last record but i can not cast it. The warning says "Cast from '[Class]' to unrelated type 'Class' always fails". Is it other way to get properties from this result ?
let _context = DataBaseController.getContext()
let _fetchRequest:NSFetchRequest<Class> = Class.fetchRequest()
do {
let _allElements = try _context.count(for: _fetchRequest)
_fetchRequest.fetchLimit = 1
if _allElements == 1 {
_fetchRequest.fetchOffset = _allElements
} else {
_fetchRequest.fetchOffset = _allElements - 1
}
_fetchRequest.returnsObjectsAsFaults = false
do {
let _result = try DataBaseController.getContext().fetch(_fetchRequest) as! Ping
} catch {
print("Error \(error)")
}
} catch {
print("Error \(error)")
}
Thanks in advance!
A Core Data fetch returns always an array of managed objects
let _result = try DataBaseController.getContext().fetch(_fetchRequest) as! [Ping]
if !_result.isEmpty {
let lastItem to _result[0]
}