Parse.com Get User Who Like an Object - ios

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.

Related

How to realize users feedback with FireBase?

I have an application where user can like photos, comment etc. Functionality like Instagram has.
I want to realize users !feedback!, where user can see information, who liked his photos, who started to follow and etc. I don't know actually how should I organize structure of my database in this situation.
My user node snapshot:
My posts node snapshot:
As I can see, I have next option - I should save all actions, which are linked to user, to his node in internal node Feedback. But how can I keep sync this? For example, someone can follow my user, I will add it to this node, user will unfollow, but the record still remains. I think, that it is wrong way.
I have no other idea actually and I can't find anything about that.
Any suggestions and solutions are much appreciated.
EDIT: I need to understand, how to realize this tab of instagram-like apps:
How to retrieve data for it from nodes?
UPD: DB Architecture in my examples is bad (old question). Be carefull (10.11.2017).
First, let's think about how we need to structure our database for this:
There are two very important principles to follow when structuring data for Firebase:
You should save your data the way you want to retrieve it.
You should keep your data structure as flat as possible - avoid nesting.
Point 1 is because Firebase is not a relational database. This means that we need to keep queries simple in order to achieve performance. Making complex queries might require many requests to Firebase.
Point 2 is because of the way Firebase's query model works: If you observe a node, you also get all the children of that node. This means that, if your data is deeply nested, you might get a lot of data you don't need.
So, having those principles in mind, let's take a look at your case. We have users, who have photos. These are the two primary entities of your database.
I can see that, currently, you are keeping your photos as properties of the users. If you want to be able to query photos by user quickly (remember Point 1), this is a good way to do it. However, if we want users to be able to "favorite" photos, a photo should be more than just a link to its Firebase Storage location: It should also hold other properties, such as which users have favorited it. This property should be an array of user IDs. In addition, for each user, you'll want to store which photos are that user's favorites. This might seem like data duplication, but when using Firebase, it's OK to duplicate some data if it'll lead to simpler queries.
So, using a data index such as in the example above, each of your Photos should look like this:
{
id: /* some ID */,
location: /* Firebase Storage URL */,
favorited_by: {
/* some user ID */: true /* this value doesn't matter */,
/* another user ID */: true,
},
/* other properties... */
}
And your user should have a favorites property listing photo IDs. Now, since every photo has a user that "owns" it, we don't need to have a unique ID for every photo, we just need to ensure that no user has two photos with the same ID. This way, we can refer to the photo by a combination of its user ID and its photo ID.
Of course, remember Point 1: If you want to be able to get user info without getting a user's photos, you should have a different property on your root object for photos instead of associating photos with users. However, for this answer, I'll try to stick to your current model.
Based on what I said above, the favorites property of a user would hold an array of values of the format 'userId/photoId'. So, for instance, if a user favorites the photo with ID "3A" of the user with ID "CN7v0A2", their favorites array would hold the value 'CN7v0A2/3A'. This concludes our structure for favorites.
Now, let's look at what some operations you have mentioned would look like under this structure:
User favorites a photo:
We get the user ID of the photo's owner
We get the user ID of the user who is favoriting the photo
We get the ID of the photo
We add the user who is favoriting's ID to the photo's favorited_by array
We add photoOwnerID + "/" photoID to the favoriting user's favorites array
If the user unfavorites the photo later, we just do the opposite: We remove photoOwnerID + "/" + photoID from the user's favorites and we remove the favoriting user's ID from the photo's favorited_by property.
This kind of logic is sufficient to implement likes, favorites, and follows. Both the follower/liker/favoriter and the followee/likee/favoritee should hold references to the other party's ID, and you should encapsulate the "like/favorite/follow" and "unlike/favorite/unfollow" operations so that they keep that database state consistent every time (this way, you won't run into any issues such as the case you mentioned, where a user unfollows an user but the database still holds the "following" record).
Finally, here's some code of how you could do the "Favorite" and "Unfavorite" operations, assuming you have a User model class:
extension User {
func follow(_ otherUser: User) {
let ref = FIRDatabase.database().reference()
ref.child("users/\(otherUser.userId)/followers/")
.child(self.userId).setValue(true)
ref.child("user/\(self.userId)/following/")
.child(otherUser.userId).setValue(true)
}
func unfollow(_ otherUser: User) {
let ref = FIRDatabase.database().reference()
ref.child("users/\(otherUser.userId)/followers/")
.child(self.userId).remove()
ref.child("user/\(self.userId)/following/")
.child(otherUser.userId).remove()
}
}
Using this model, you can get all the follower user IDs for a user querying that user's followers property and using the .keys() method on the resulting snapshot, and conversely for users a given user follows.
Added content: We can build further on this structure in order to add simple logging of actions, which seems to be what you want to have available to the user in the "Feedback" tab. Let's assume we have a set of actions, such as liking, favoriting and following, which we want to show feedback for.
We'll follow point 1 once again: In order to structure feedback data, it is best to store this data in the same way we want to retrieve it. In this case, we will be most often showing a user their own feedback data. This means we should probably store feedback data by user ID. Additionally, following point 2, we should store feedback data as its own table, instead of adding it to the user records. So we should make a new table on our root object, where for each user ID, we store a list of feedback entries.
It should look something like this:
{
feedback: {
userId1: /* this would be an actual user ID */ {
autoId1: /* generated using Firebase childByAutoId */ {
type: 'follow',
from: /* follower ID */,
timestamp: /* Unix time */,
},
autoId2: {
type: 'favorite',
from: /* ID of the user who favorited the photo */
on: /* photo ID */
timestamp: /* Unix time */
},
/* ...other feedback items */
},
userId2: { /* ...feedback items for other user */ },
/* ...other user's entries */
},
/* other top-level tables */
}
In addition, we will need to change the favorites/likes/follows tables. Before, we were just storing true in order to signal that someone liked or favorited a photo or followed a user. But since the value we use is irrelevant, as we only check keys to find what the user has favorited or liked and who they have followed, we can start using the ID of the entry for the like/favorite/follow. So we would change our "follow" logic to this:
extension User {
func makeFollowFeedbackEntry() -> [String: Any] {
return [
"type": "follow",
"from": self.userId,
"timestamp": UInt64(Date().timeIntervalSince1970)
]
}
func follow(_ otherUser: User) {
let otherId = otherUser.userId
let ref = FIRDatabase.database().reference()
let feedbackRef = ref.child("feedback/\(otherId)").childByAutoId()
let feedbackEntry = makeFollowFeedbackEntry(for: otherId)
feedbackRef.setValue(feedbackEntry)
feedbackRef.setPriority(UInt64.max - feedbackEntry["timestamp"])
let feedbackKey = feedbackRef.key
ref.child("users/\(otherUser.userId)/followers/")
.child(self.userId).setValue(feedbackKey)
ref.child("user/\(self.userId)/following/")
.child(otherUser.userId).setValue(feedbackKey)
}
func unfollow(_ otherUser: User, completionHandler: () -> ()) {
let ref = FIRDatabase.database().reference()
let followerRef = ref.child("users/\(otherUser.userId)/followers/")
.child(self.userId)
let followingRef = ref.child("user/\(self.userId)/following/")
.child(otherUser.userId)
followerRef.observeSingleEvent(of: .value, with: { snapshot in
if let followFeedbackKey = snapshot.value! as? String {
// we have an associated follow entry, delete it
ref.child("feedback").child(otherUser.userId + "/" + followFeedbackKey).remove()
} // if the key wasn't a string, there is no follow entry
followerRef.remove()
followingRef.remove()
completionHandler()
})
}
}
This way, we can get a user's "feedback" just by reading the "feedback" table entry with that user's ID, and since we used setPriority, it will be sorted by the most recent entries first, meaning we can use Firebase's queryLimited(toFirst:) to get only the most recent feedback. When a user unfollows, we can easily delete the feedback entry which informed the user that they had been followed. You can also easily add extra fields to store whether the feedback entry has been read, etc.
And even if you were using the other model before (setting "followerId" to true), you can still use feedback entries for new entries, just check if the value as "followerId" is a string as I have done above :)
You can use this same logic, just with different fields in the entry, to handle favorites and likes. When you handle it in order to show data to the user, just check the string in the "type" field to know what kind of feedback to show. And finally, it should be easy to add extra fields to each feedback entry in order to store, for instance, whether the user has seen the feedback already or not.
You can sort of implement what you want by using Firebase Functions. Here's roughly how I would go about implementing it:
All a user's feedback will be stored in /Feedback/userID/, located at the root.
Within this node, have a subnode called eventStream.
Whenever an action occurs, this can be directly added to the user's eventStream, ordered by time.
This action could be of the form: pushID: { actionType:"liked", post:"somePostID", byUser:"someUserId" }
Also include an anti-action subnode (under /Feedback/userID/). Whenever one of these 'anti-action' events occurs (for example: unlike, unfollow etc.), store this under the anti-action node for the corresponding user. This node will essentially act as a buffer for our function to read from.
This anti-action could be of an almost identical form: pushID: { actionType:"unliked", post:"somePostID", byUser:"someUserId" }
Now for the function.
Whenever an anti-action is added to the anti-action node, a function removes this from the anti-action node, finds the corresponding action in the eventStream, and removes this. This can be achieved easily by first querying by "actionType" then "someUserId" and then by "somePostID".
This will ensure that the user's eventStream will always be up to date with the latest events.
Hope this helps! :)

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

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.

iOS Parse query with mixed objects

I have a chat application working with Parse.com.
In my app users able to like posts. Like button has liked and normal states. So the next time when the user open the app I should show the posts that user liked with liked button state.
Server objects:
1) User
2) Post
3) Like
User object columns:
1. Likes(relation) - all liked posts by the user
2. Posts(relation) - all user posted posts
Post object columns:
1. Users(pointer) - created by user pointer
2. Likes(relation) - all existing likes on the post
Like object columns:
1. User(pointer) - liked by user
2. Post(pointer) - liked post
/**** PROBLEM ****/
Now I'm getting all posts from the "Posts" table, then getting the all liked posts from the "User's Liked posts relation" and if the post from the Posts table exists in the "User's Liked posts relation" I'm changing the Like button state to Liked.
I don't like how I'm doing it as now I'm calling two requests for showing the posts Like button state correct. I'm sure that it's possible to do with single request, but don't know how.
Can someone help me please ???
I think you do not need to use any data from your User class to deduce what you want. You can simply get the associated Likes data for each Post object in a single query. Then process the Likes information for each one and see if the current user is there to set the Like button state:
// Get all the posts
let postQuery = PFQuery(className: "Post")
// Include all like objects to for each post as well
postQuery.includeKey("Likes")
// execute the query
postQuery.findObjectsInBackgroundWithBlock{
(objects: [AnyObject]?, error: NSError?) -> Void in
// objects are all of the Post objects, and their associated Like objects, too
// Process data for each post and check the likes info to see if your current
// user is there, then set the Like button accordingly
}

Contacts/Friends list functionality in Swift

I am working on an iOS application that allows users to send their location to a handful of their friends. Where I'm having difficulty is the actual friends list functionality. All my research just results in getting your friends list from Facebook. This is a proprietary friends list within my app, similarly to Snapchat. I do not want to get friends through another party. Users are stored using Parse so it would be preferred to have my friends list logic using Parse. Any help is appreciated. Thank you.
So you want people using your app to be able to send friend requests and become friends?
I did this with a Relation column in Parse named "Friends", in the User class.
To send and receive requests, I made a class "FriendReqeust" with two Pointer columns, "fromUser" and "toUser", and a String, "status".
Display the friend request properly and add buttons for accept and decline. It's easy to create a new relation for the accepting user, and add that one in to his/hers Relation column, but to add it to the FriendRequestSender you need to use Cloud Code.
Example Code for accepting request (friend added to currentUser if Cloud Code succeeds):
PFCloud.callFunctionInBackground("addFriendToFriendsRelation", withParameters: ["friendRequest":friendRequestId]) {
(response: AnyObject?, error: NSError?) -> Void in
if error == nil {
var friendsRelation = self.toUser!.relationForKey("friendRelations")
friendsRelation.addObject(self.fromUser!)
self.toUser!.saveInBackground()
} else {
println(error)
}
}
If you know how to translate Objective-C to Swift, I recommend the answer to this question.
https://teamtreehouse.com/forum/parsecom-swift-how-do-you-add-pfusercurrentuser-to-another-users-relations-using-cloud-code-the-master-key
Then you can query Friends from any user.

How to retrieve user's friend list and show in TableView with swift

I'm using parse and I'm able to save user's friend that user added in current user's object but I don't know how to retrieve current user's friend list and show them in tableview so how can I get that list from current user's object and display it in tableview with parse in swift. (My app doesn't have anything to do with Facebook at all).
Any help is appreciated.
Thank you
EDIT: The OP posted some code in the comments, i formatted it into the question:
var query = PFUser.query();
var currentUser = PFQuery(className:"User");
currentUser.whereKey("currentUser", equalTo: PFUser.currentUser()) currentUser.findObjectsInBackgroundWithBlock { (objects:[AnyObject]!, error:NSError!) -> Void in
if error == nil {
self.tableView.reloadData()
}
I've managed to save friend that user added in PFRelation but I can't manage to show user list in my tableview.My tableview didn't show any username of the current user account
you may want to check this post on parse.com, which explains how findObjectsInBackgroundWithBlock: works:
https://www.parse.com/questions/how-to-use-findobjectsinbackgroundwithblock-properly-and-be-able-to-cache-the-result
Long story short, you seem to not be updating the tableViews dataSource before calling reloadData. make the array that populates yout tableVIew contain the objects that findObjectsInBackgroundWithBlockreturns, then call reloadData. Best of luck and welcome to SO!

Resources