This is my query that will return all of my users in the datastore:
func getAllUsers(){
let query = GTLQueryUser.queryForUserList()
service.executeQuery(query, completionHandler: {(ticket, response, error) -> Void in
if error != nil {
print(error)
}
else{
let userCollection = response as! GTLUserCollection
if let newUsers = userCollection.items() as? [GTLUserUser]{
users = newUsers
print("These are the users that are in data store")
for user in users{
print (user.firstName)
}
}
}
})
}
How do I set the query limit to one so that I can only query one user at a time?
You have to specify in the query fields in your age that you can even enter a limit. Like so:
#User.query_method(path="user/list", http_method="GET", name="user.list", query_fields=("limit", "user_bucket", "age","gender", "order", "pageToken"))
Then you generate client code with Google's discovery generated then you can go into the swift app and type:
query.limit = 1
Related
Okay, after some comments for more information to this question, let's try it:
In a viewController I'm calling a function loadPosts().
In this function I'm calling api.shared.function observePosts().
observePosts() downloads all posts at a specified database reference. with the first downloaded post back in the loadPosts() I'm calling self.fetchUser(uid: post.uid) in VC. This call api.shared.function observeUser() which gives me the user from the current post downloaded.
In observeUser() I'm filling users-array with user-object from type UserModel and in completion-block of loadPosts() I'm filling posts-array with post-object from type PostModel. After insert the post to posts-array at:0 I reload the tableView and get a view of all downloaded posts with matching info about the user that created it. (code comes after description)
Means: If there are 5 posts from type PostModel - there must be 5 userModel's, too. Every function is called 5 times - in this example with 5. Every function is running 5 times. (Tested with a print("call loadPosts")).
What I finally want is:
Get the posts and the users - fill the arrays (when all posts at this time) are loaded. Make this step again - with posts2 and users2. If all four arrays are filled - an algorithm get them together in one posts-array and one users-array, like:
post[0], post1, post[2], post2[0], post[3], post[4], post[5], post21 ...
Of course the users will be putting together in one as well.
THIS PART IS NO PROBLEM! I HAVE A WORKING CODE ... with hardcoded strings in the arrays
My problem is:
When I'm calling a function at the end of a function it can be fact that the asynchronous task of loading the posts ( users ) is not done. So, how can I make the code "wait to complete"? I have tried this from StackOverflow and also tried to work with DispatchQueue but that don't work, too.
Before the code is coming - here is my workaround:
xCode 11.3.1 with Swift 5. ( async / await - solutions from the comments are not possible )
Code(s):
In ViewController:
viewDidLoad => loadPosts()
loadPosts(){
Api.shared.observePosts { (post) in
self.fetchUser(uid: UserUid, completed:{
self.posts.insert(post, at:0)
self.tableView.reloadData()
})
}
}
func fetchUser(uid: String, completed: #escaping () -> Void) {
Api.shared.observeUser(uid: uid) { (user) in
self.users.insert(user, at: 0)
completed()
}
}
In Api
// load posts
func observePosts(completion: #escaping (PostModel) -> Void){
REF_POSTS.observe(.childAdded) { (snapshot) in
guard let dic = snapshot.value as? [String: Any] else { return }
let newPost = PostModel(dictionary: dic, key: snapshot.key)
completion(newPost)
}
}
// load users
func observeUser(uid: String, completion: #escaping(UserModel) -> Void){
REF_USERS.child(uid).observeSingleEvent(of: .value) { (snapshot) in
guard let dic = snapshot.value as? [String: Any] else { return }
let newUser = UserModel(dictionary: dic)
completion(newUser)
}
}
I hope that now is clear what I'm doing and what I want to to after modifying the code. Any help or info is welcome.
The question is a bit vague but what I think is being asked is:
I have two Firebase nodes (collections in Firestore), a post node and a user node. The post node contains child nodes that have a node that stores the uid of the user which are found in the users node. I want to load all the posts and the user associated with each post
If that's the question, here's a simplified solution:
First a structure to store the post and users name
struct PostAndUserStruct {
var post = ""
var userName = ""
}
and then a class var to use as a tableView dataSource
var postsAndUsersArray = [PostAndUserStruct]()
and then the code to read it in and populate that datasource for use with the tableView
func readPostsAndUser( completion: #escaping () -> Void ) {
let postsRef = self.ref.child("posts")
let usersRef = self.ref.child("users")
postsRef.observeSingleEvent(of: .value, with: { snapshot in
let allPosts = snapshot.children.allObjects as! [DataSnapshot]
let lastPostIndex = allPosts.count - 1
for (index, post) in allPosts.enumerated() {
let postText = post.childSnapshot(forPath: "post_msg").value as? String ?? "No Msg"
let uid = post.childSnapshot(forPath: "post_uid").value as? String ?? "No User"
let thisUser = usersRef.child(uid)
thisUser.observeSingleEvent(of: .value, with: { userSnapshot in
let name = userSnapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
let postAndUser = PostAndUserStruct(post: postText, userName: name)
self.postsAndUsersArray.append(postAndUser)
if index == lastPostIndex {
completion()
}
})
}
})
}
The flow code flow is
The postsAndUsersArray would be a tableView datasource.
Load all of the posts and cast the snapshot to an array to preserve ordering. Keep track of how many there are
Iterate over each post, capturing the post msg as well as the uid of the user that made the post.
Read each user, instantiate a PostAndUserStruct and add it to the class array (the tableViewDatasource)
When the loop gets to the last index, call the completion handler and output the results to console (good place to reload the tableView)
and is called like this
func handleButton0Action() {
self.readPostsAndUser(completion: {
for post in self.postsAndUsersArray {
print(post.userName, post.post)
}
//this is a good place to reload the tableview
})
}
The "note to self" on this is it's not super scaleable with large datasets but gets the job done for small dataset. If you have a millions posts you will need to implement pagination to read a chunk of posts at a time, otherwise you'll overload the device's memory and have random crashes.
NOTE: BELOW IS FIRESTORE CODE
The code to read the posts, read in the user for each post and return a populated array of PostAndUserStruct objects, noting self.db points to MY Firestore.
func readPostsAndUsersAsync() async -> [PostAndUserStruct] {
let postsCollection = self.db.collection("posts")
let usersCollection = self.db.collection("users")
var postDataArray = [PostAndUserStruct]()
let postSnapshot = try! await postsCollection.getDocuments()
let allPosts = postSnapshot.documents
for post in allPosts {
let postText = post.get("post_msg") as? String ?? "No Post Message"
if let uid = post.get("post_uid") as? String {
let userDoc = usersCollection.document(uid)
let userSnap = try! await userDoc.getDocument()
let name = userSnap.get("name") as? String ?? "No Name"
let postData = PostAndUserStruct(post: postText, userName: name)
postDataArray.append(postData)
}
}
return postDataArray
}
The code flow:
All of the documents are read from Firestore with getDocuments. Then we cast those documents to the allPosts array.
Iterate over the array, capturing the post_msg field from each post as well as the uid of the use that posted it.
Then, using the uid, read the users document from the users collection and get the users name.
Instantiate a PostAndUserStruct object with the post and users name and add it to an array.
Then return the populated array
That function is called like this and outputs the users name and post text to console.
func fetchPostsAndUsers() {
Task {
let postResults = await self.readPostsAndUsersAsync()
for postAndUser in postResults {
print(postAndUser.userName, postAndUser.post)
}
}
}
I am retrieving data from my Firestore database using a 'getDocuments' query, however I am retrieving an empty array. Through debugging, I have used a print(snapshot?.documents) and found an empty array returned. Can someone help as to why I am returning an empty array?
getNotifications function:
public func getNotifications(
completion: #escaping ([IGNotification]) -> Void
) {
guard let username = UserDefaults.standard.string(forKey: "username") else {
completion([])
return
}
let ref = firestoreDatabase.collection("users").document(username).collection("notifications")
ref.getDocuments { snapshot, error in
guard let notifications = snapshot?.documents.compactMap({
IGNotification(with: $0.data())
}),
error == nil else {
completion([])
return
}
completion(notifications)
}
}
Firestore data:
Screenshot of Firestore data
I cant seem to get certain records to modify in my public database. The error is
Error saving record <CKRecordID: XXXXXXX; recordName=XXXXXX, zoneID=_defaultZone:defaultOwner> to server: WRITE operation not permitted
I don't understand why it says "Write not permitted" because I have all the correct Security Roles checked off in the dashboard. I also confirmed that it is signed into Icloud before I try to modify the record.
This is the relevant code:
// MARK: - Modify Updates in Cloudkit
static func modifyUpdates(item: pushNote, completion: #escaping (Result<pushNote, Error>) ->
()) {
guard let recordID = item.recordID else { return }
CKContainer.default().publicCloudDatabase.fetch(withRecordID: recordID) { (record, err) in
DispatchQueue.main.async {
if let err = err {
completion(.failure(err))
return
}
guard let record = record else { return }
record["updates"] = item.updates as CKRecordValue
CKContainer.default().publicCloudDatabase.save(record) { (record, err) in
DispatchQueue.main.async {
if let err = err {
completion(.failure(err))
return
}
guard let record = record else { return }
let id = record.recordID
guard let updts = record["updates"] as? [String] else { return }
guard let boss = record["bossID"] as? String else { return }
let element = pushNote(recordID:id, bossID: boss, updates : updts)
completion(.success(element))
}
}
}
}
}
Saving Security Role changes takes time to kick it. Like 10min for me. I recommend not clicking Save too many times. Log out and back in to see if it is actually saved.
Basically, instead of pulling the most recent saved object, I'd like to pull out previously saved objects. How would I go about doing this? What the code is doing is setting the text of a label depending on the object's text pulled from the array, but I want to pull older saves.
// deleteAllRecords() // ---- uncomment deleteAllRecords() here to delete all records saved.
// FETCH BLOCK
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Tasks")
//request.predicate = NSPredicate(format: "age = %#", "12")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [Tasks] {
print(data.value(forKey: "taskName"))
if let reminders = data.value(forKey: "taskName") as? [Reminder] {
for reminder in reminders {
// Now you have your single object Reminder and you can print his variables
print("Your reminder description is \(reminder.reminderDescription), and his length is \(reminder.reminderLength)")
self.reminderDisplay.text = reminder.reminderDescription
if reminderDisplay.text == reminder.reminderDescription {
self.testLabel1.text = reminder.reminderDescription
reminderCount = 1
}
}
}
}
How would I go about doing this?
Thanks in advance. - Sav.
EDIT:
// SAVE BLOCK
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Tasks", in: context)
let newTask = Tasks(entity: entity!, insertInto: context)
newTask.setValue(reminderList, forKey: "taskName")
do {
try context.save()
} catch {
print("Failed saving")
}
}
EDIT2:
request.predicate = NSPredicate(format: "taskName = %#", "Lock the door.")
EDIT3:
Here's the object code;
public class Reminder: NSObject, NSCoding {
var reminderDescription : String? = nil
var reminderLength : Int? = nil// in days
public required init?(coder aDecoder: NSCoder) {
self.reminderDescription = aDecoder.decodeObject(forKey: "reminderDescription") as? String
self.reminderLength = aDecoder.decodeObject(forKey: "reminderLength") as? Int
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(reminderDescription, forKey: "reminderDescription")
aCoder.encode(reminderLength, forKey: "reminderLength")
}
init (chosenReminderDescription: String, chosenReminderLength: Int) {
reminderDescription = chosenReminderDescription
reminderLength = chosenReminderLength
}
}
Data Model
If possible I'd like to query in relation to when they were made, like the first one, then the 2nd one. Would I have to give the object a date property and query through that?
EDIT4:
request.predicate = NSPredicate(format: "orderNo = %#", "2")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [Tasks] {
print(data.value(forKey: "taskName"))
if let reminders = data.value(forKey: "taskName") as? [Reminder] {
for reminder in reminders {
// Now you have your single object Reminder and you can print his variables
print("Your reminder description is \(reminder.reminderDescription), and his length is \(reminder.reminderLength)")
self.reminderDisplay.text = reminder.reminderDescription
if reminderDisplay.text == reminder.reminderDescription {
self.testLabel1.text = reminder.reminderDescription
reminderCount = 1
}
}
}
}
Your code seems to call a request at entity Task and get the results in an Array correctly.
I would suggest you to look if you are actually saving the previous attempts to store value at your Core Data entity Task before asking for queries like this.
Don't forget that sometimes Core Data decides to postpone actual storing of information on its DB.
So try forcing savings into CoreDB before querying. For this to happen, make sure you are calling ".save()" to guarantee that Core Data sends (and executes) a saving request right away.
A sample code to force saving right away is down below:
do {
try context.save()
} catch {
print("Failure to save context: \(error)")
}
}
–
Edit (24/11/2018 03:07AM GMT+01:00):
By reading your comment, it looks like you just want to filter the queries for a specific value, instead of querying the most recent ones.
To do so, just simply uncomment the code:
//request.predicate = NSPredicate(format: "age = %#", "12")
To:
request.predicate = NSPredicate(format: "age = %#", "12")
Inside the NSPredicate's format method calling is supposed to be what you want as the statement of your filtering. If you want to filter it by age, your example is already done for you.
I'll also attach an example of other codes of mine.
request.predicate = NSPredicate(format: "(time >= %#) AND (time <= %#)", fromDateUnformatted as NSDate, toDateUnformatted as NSDate)
Now if you want to do relationship-related queries (double or more queries at once to dig information further inside Entities' relationships), then there are other methods of doing so.
Let me know if I'm going in the right direction to find an answer.
I have a question related to load data from Firestore to table view. Basically, i understand how to do it, but in this case, i kind of confuse if the structure of Firestore as below:
"fund" -> "randomId" -> "agensi: id"
i was able to load the agensi from collection which is "WJ18kBvDLhVrvml2slXB". To get the real agensi name i have to get data from collection "Agensi" as image below:
below is the code that what i have already did:
var agencyname: [String] = []
func getData() {
db.collection("fund").getDocuments()
{
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
// var agencyNumber = 0
for document in querySnapshot!.documents {
let data = document.data()
let agency = data["agensi"] as? String ?? ""
let agencyId = document.documentID
print(agency)
print(agencyId)
//MARK get name
let newdocRef = Firestore.firestore().document("Agensi/\(agency)")
newdocRef.getDocument { (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else { return }
let dataDetails = docSnapshot.data()
let agencyNew = dataDetails!["name"] as? String ?? ""
self.agencyname.append(agencyNew)
print("List of the agency: \(self.agencyname.append(agencyNew))")
}
}
self.tableView.reloadData()
}
}
}
i try to print:
self.agencyname.append(agencyNew)
but its display nothing. so, i cannot load the name of the agency into my table view cell. By the way sorry for my bad english. Thanks in advance
There are few things I would like to tell:
1. FireStore queries are asynchronous so there is a callback function when it finishes. Reload your tableView inside the callback instead of end of the loop only then it will load the data properly.
DispatchQueue.main.async {
self.tableView.reloadData()
}
Write above code just below print("List of the agency: \(self.agencyname.append(agencyNew))") line.
2. You're printing "self.agencyname.append(agencyNew)" and it is void by the way so will not print anything so print(\(self.agencyname)) instead.
3. When using a guard in a loop then don't use return because it will break the loop for next iteration if any error occurs. We should use continue here to let the loop execute completely.