Parse & Swift - How to Reduce Server Load/Requests? - ios

I've been developing an application that has a home page, similar to that of Instagram's where the number of comments is displayed below a post as the user scrolls. Once the user taps on the label with the number of comments, the actuals comments will then load onto the screen. I've been trying to achieve something similar to that with my application, but I feel as if the method that I am doing this with is sending to many requests (queries) to the server in order to get the number of comments to display below each post. I was wondering if there was a more efficient/concise way to do this that would reduce the server load, but still have the same effect.
To add more context (Note: I'm using Parse):
I have a class named Posts which contains the posts
I have a class named Comments which contains all of the submitted comments. In order to obtain the comments for a particular post, I query for comments (which have a column named: "parentObjectID") whose column ("parentObjectID") matches with the parent post's object ID.
Example code is below:
let query = PFQuery(className: "Comments")
query.whereKey("parentObjectID", equalTo: objectIDs[indexPathNums])
query.order(byDescending: "createdAt")
query.findObjectsInBackground { (objects, error) in
if error != nil {
print("An error occured (USQVC Comments Query)")
}else {
if let tempArray = objects {
for comment in tempArray {
if let x = comment {
myArray.append(x)
}
}
commentsCount = myArray.count
myArray.removeAll()
}
}
}
Even though they've updated their UI, I just wanted to include a picture in case what I was describing was unclear:
I'd appreciate it if anybody could help me out with this. Thanks!

As you pointed out in your comment above, you certainly can use the function incrementKey to increment the numberOfComments.
The best part is, this method is atomic, that means no matter how many people like the same post at the same time, say 5 people like a photo at exact same time, numberOfComments will increment by 5 instead of 1.
So this function will be executed one by one instead of concurrently. You absolutely can use it.

Related

ios - Repeating Keys when paginating with Firebase

I am learning pagination with Firebase. I am using a method in which I store the key of the last added item in the previous page, so the next page can continue from there.
The problem is that when using ref.queryStarting(at value: lastItemKey) to keep retrieving items from the last added key, the last item gets repeated twice (since queryStarting is inclusive).
And so if I limit to 5 the query I would end up with only 4 new items as 1 would be a duplicate.
The only solution I came up is requesting one more item and remove the repeated one, but I wonder if it´s efficient at all doing it this way. (since we are wasting one item in each query)
If it´s any help, my code looks like this:
// rest of the pages
if let lastItemID = lastItemKey {
itemPageRef = self.itemsRef.queryOrderedByKey().queryStarting(atValue: lastItemID)
.queryLimited(toFirst: UInt(amount))
} else {
// First page of data: we retrieve the first (amount) items
print("We are in the first page of DATA")
itemPageRef = self.itemsRef.queryOrderedByKey().queryLimited(toFirst: UInt(amount))
}
itemPageRef.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in
Requesting an overlapping child node between the pages is the only way the Firebase API supports. Since there is no other way to do this, there isn't a more efficient way.
That said, it's typically quite efficient, especially if you use a page size of 25+ child nodes, which is also more reasonable on most use-cases I've seen.

Cross reference columns in Parse to decide which users to Display on a view controller. Swift, user search settings

What I am trying to figure out is how to only display users that meet the setting requirements that the user previously saved on a different ViewController. Ok so on set up the current user has selected their genre and instrument and it has saved in columns in Parse called "genre" and "instrument" as Strings. Then on the search settings page the user has selected that they would like to search for lets say "Rock" as the genre and "Acoustic guitar" as the instrument. Both of these then get added to Parse under the columns "genreSearch" and "instrumentSearch".
So I know I need to make a query and display it on the ViewController that the users are displayed on but I don't know how. I am trying to basically cross reference the column "genre" of other users against the current users column "genreSearch". I imagined it would be something like this:
genreQuery.whereKey("username",notEqualTo:PFUser.currentUser()!.username!)
genreQuery.whereKey("genre", notEqualTo:PFUser.currentUser()!username!)
genreQuery.whereKey("genreSearch", equalTo:PFUser.currentUser()!)
genreQuery.findObjectsInBackgroundWithBlock { (users: [AnyObject]?,
error: NSError?) -> Void in
if error == nil {
for user in users! {
if self.genre == self.genreSearch {
print("These two strings are considered equal")
appUsers.append(user as! PFUser)
}
self.resultsPageTableView.reloadData()`
At the top of my VC I have as I am storing and displaying all the users in a cell which also links to another VC to show more details.
var genre = [String]()
var genreSearch = [String]()
var appUsers = [PFUser]()
I have read Parse docs and to be honest now I am more confused as where to go.
I have searched the internet for past few days and it is all js and objc both of which I have zero experience in. If someone could point me at a start or even guide me in what to do so I can learn.
In the cell I am displaying the users details like so:
let singleCell: CustomCell = tableView.dequeueReusableCellWithIdentifier("mySingleCellid") as! CustomCell
let userObject = appUsers[indexPath.row] as PFObject
singleCell.userName.text = userObject["name"] as? String
// etc
return singleCell
This part of the query
genreQuery.whereKey("genreSearch", equalTo:PFUser.currentUser()!)
is wrong because you're trying to check that an array of strings contains a user object (a pointer) - which will always fail.
While you have genreSearch on the server for the current user it's easier to just replace that part of the query with
genreQuery.whereKey("genreSearch", containedIn:PFUser.currentUser()!["genreSearch"])
which instead asks for the genreSearch array on each user tested to contain at least one of the current users array of genreSearch
I was going about it all wrong what I needed to do was take genreSearch from the Parse DB and store it in the app itself as a variable. This variable is then a key for my PFQuery and I use it to filter out the people that don't have it. I then use genreSearch as a condition skipping the people that don't have it and adding the people that do. I nearly have it cracked except for the last few coding of it. Instead of editing this question to ask for help I have asked and posted my new code to a new SO question

Parse.com Get User Who Like an Object

So I've set up my relations correctly in Parse using the following code:
var user = PFUser.currentUser()
var relation = user.relationForKey("likes")
relation.addObject(self.post)
user.saveInBackgroundWithBlock({ (Bool, error) -> Void in
println("user likes this post")
// Used for tracking in the PFTableCell for UI parts
self.userLikes = true
})
When the view loads, I need to change the buttons to Liked and the colour etc. I'd like to get all the users who liked this particular post but cannot figure out the query? It will give me some other useful info (e.g. I could display usernames of people who liked the post).
As a workaround I can get all the posts the user likes by doing:
var user = PFUser.currentUser()
var relation = user.relationForKey("likes")
relation.query().findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error != nil {
// There was an error
println(error)
} else {
// objects has all the Posts the current user liked and do something.
println(objects)
}
}
But that's not what I'm after? I'm using Swift and the Parse SDK, but will happily have the answer in Obj C and I'll translate. The Parse docs suggestion is wrong: https://www.parse.com/docs/ios_guide#objects-pointers/iOS I could store the like against the post rather than the user, but that would mean I'd have to open up access rights to the post which is a no-no
There is an issue with your database structure. You should not just have User objects, but you should have Post objects as well. The Post can have a likes relation that points to all the Users who liked the post. When a User likes the Post, then the User is added to the Post's likes relation. Thus, to get the info you need, you just need to look at the Post's relation.
What do you mean about open access rights to the Post? Do you want the post to be read only for all users except the User who posted it?
Or is the issue with giving access to the User objects? I am not sure if it really an issue, since you could just be running a quick count on the User objects in the relation without actually looking at the data in the User objects. You could create Like objects, but I don't think it's necessary.

Query users by name or email address using Firebase (Swift)

I'm quite new to Firebase and Swift and I'm having some trouble when it comes to querying.
So there are basically two things I'd like to do:
Query my users and find only those that contain a certain String in their name (or email address) and add them to an array.
Get all of my users and add them to an array.
The relevant part of my data for this question looks like this:
As you can see, I'm using the simplelogin of Firebase (later I'd like too add Facebook login) and I'm storing my users by their uid.
A part of my rules file looks like this:
"registered_users": {
".read": true,
".write": true,
".indexOn": ["name"]
}
So everybody should have read and write access to this part of my data.
I also read the "Retrieving Data" part of the Firebase iOS Guide on their website and according to that guide, my code on getting all the users names and email addresses should work, at least I think so. But it doesn't. Here is my code:
func getUsersFromFirebase() {
let registeredUserRef = firebaseRef.childByAppendingPath("registered_users")
registeredUserRef.queryOrderedByChild("name").observeSingleEventOfType(.Value, withBlock: { snapshot in
if let email = snapshot.value["email"] as? String {
println("\(snapshot.key) has Email: \(email)")
}
if let name = snapshot.value["name"] as? String {
println("\(snapshot.key) has Name: \(name)")
}
})
}
I noticed, that in the firebase guide, they always used the type ChildAdded and not Value, but for me Value makes more sense. The output with Value is nothing and the output with ChildAdded is only one user, namely the one that is logged in right now.
So my questions are:
Can I do this query with my current data structure or do I have to get rid of storying the users by their uid?
If yes, how would I have to change my code, to make it work?
If no, what would be the best way to store my users and make querying them by name possible?
How can I query for e.g. "muster" and get only the user simplelogin:1 (Max Mustermann)?
I hope my description is detailed enough. Thx in advance for your help.
Supplement:
The weird thing is, that the "Retrieving Data" guide says, that querying and sorting the following data by height is possible.
Data:
Querying code:
And isn't that exactly the same that I intent to do?
I have run into similar situations where I wanted to pull out data from child nodes.
https://groups.google.com/d/msg/firebase-talk/Wgaf-OIc79o/avhmN97UgP4J
The first thing I can recommend is to not think of Firebase query's as SQL queries as they are not. They are like a light duty query.
Secondly, you need to flatten your data if you want to query, as a query only goes one level deep (can't really query data in child notes)
Lastly - if you don't want to flatten your data, one conceptual option to answer your question;
If possible, ObserveSingleEventOfType:FEventTypeValue on the
registered users node. This will read all of the registered users into a snapshot.
Iterate over the snapshot and read each user into an array (as dictionary objects)
Then use NSPredicate to extract an array of users that you want.
I've run numerous tests and performance wise, it's negligible unless you have thousands of users.
Hope that helps!
To answer your questions
1) Yes, you can query with your current structure. A query can go 1 child deep, but not within a child's children.
2) If yes, how would I have to change my code, to make it work?
Here's a quickie that queries by a users last name:
Firebase *usersNodeRef = your users node 'registered_users'
FQuery *allUsersRef = [usersNodeRef queryOrderedByChild:#"lastName"];
FQuery *specificUserRef = [allUsers queryEqualToValue:#"aLastName"];
[specificUser observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
NSDictionary *dict = snapshot.value;
NSString *key = snapshot.key;
NSLog(#"key = %# for child %#", key, dict);
}];
How can I query for e.g. "muster" and get only the user simplelogin:1 (Max Mustermann)?
In your uses node structure, the users are store in nodes with a key.. the key is the simplelogin:1 etc. snapshot.key will reveal that. So it's key/value pair deal...
value = snapshot.value
key = snapshot.key

How to Access Pointer Values in Parse Database

I am working on an iOS app, running on Parse(backend).
I am having problems with accessing the contents of another class from a query I made.
So I have this table. Let's call it "Contests". it has the following data:
name,
date start,
date end,
pointer to organization table (the objectid)
And then the organization table:
name,
number of Facebook likes,
I want to be able to access the name of the organization and every other detail a certain contest has. Will I have to put a query inside another query, slight problem with that is that the queries require waiting time and it accomplishes it in the background. So I have:
findInBackground() {
findInBackground() {
}
}
Is there any better way to do this? Also I am getting multiple objects at the same time.
You need to do a query on your Contest table with whatever requirements you have but then add an includeKey call:
var query = PFQuery(className:"Contests")
//...Other query requirements
query.includeKey("organization")
query.findObjectsInBackgroundWithBlock {
}
includeKey will force fetch of the organization along with the contest details in 1 query.

Resources