Problems with Pointer<Class> in Parse.com - ios

I've been using Parse.com for two months, but this is the first time I get a stupid error which I can't figure out.
I've been working in an app for iOS, but in the beginning I've been working with my own user. Now I registered other users and I want to fetch them from the server.
Well, I add one object in the table Changes, where I have a user property which is a Pointer<User> (it's not _User, but User, it's custom, just an object). Well, when I try to fetch all the rows I have, the one that I have with my user are ok:
so in my debug console is like :
but when I fetch other users:
my debug console is:
so there's not any variable!!!! :(
This is my code:
PFQuery *closestChanges = [PFQuery queryWithClassName:#"Changes"];
[closestChanges whereKey:#"coordinates" nearGeoPoint:geoPoint withinKilometers:0.5f];
[closestChanges findObjectsInBackgroundWithBlock:^(NSArray *changes, NSError *error) {
arrayChanges0 = [[NSMutableArray alloc] init];
arrayChanges1 = [[NSMutableArray alloc] init];
if (changes == nil && changes.count == 0) {
[_tableView reloadData];
return;
}
for (int i = 0; i < changes.count; i++) {
PFObject *currentChange = [changes objectAtIndex:i];
PFObject *user = [currentChange valueForKey:#"user"]; // here my user is null when it's other users.
PFObject *changeToStore = [PFObject objectWithClassName:#"Changes"];
[changeToStore setValue:currentChange[#"changetype"] forKey:#"type"];
[changeToStore setValue:currentChange[#"quantity"] forKey:#"quantity"];
[changeToStore setValue:currentChange[#"date"] forKey:#"enddata"];
[changeToStore setValue:user forKey:#"user"];
if ([currentChange[#"changetype"] intValue] == 0)
[arrayChanges0 addObject:changeToStore];
else
[arrayChanges1 addObject:changeToStore];
}
[_tableView reloadData];
}];
Is there anything wrong I'm doing by adding new users???
Thank you very much in advance.

When you fetch a pointer type from a table, you will only get back the metadata which is objectId. You need to call - (instancetype)includeKey:(NSString *)key in order to get all the data back from Changes table. If you query directly from User table, you will get all the data. But in this case, you User object is a subdocument from Changes objects.
Add this line before performing querying:
[closestChanges includeKey:#"user"]

Related

Firebase on ios is slow in retrieving data

I've read that it's important to keep data flatter for Firebase and to also only nest data that you intend to call. I've done those things, but Firebase is still too slow at retrieving data. Here's an example:
My data looks like this:
--English
----Ari : 4
----Philip : 2
----John : 6
And my code looks like this:
[super viewDidLoad];
[[DataSource sharedInstance].selectedLanguageMutableArray removeAllObjects];
//Retrieving Data From Firebase
NSString* selectedLanguagePath = [NSString stringWithFormat:#"languages/%#", [DataSource sharedInstance].languageSelected];
Firebase *languagesRef = [[DataSource sharedInstance].ref childByAppendingPath:selectedLanguagePath];
[[languagesRef queryOrderedByValue] observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
[self.distanceMutableArray addObject:snapshot.key];
NSLog(#"%#", snapshot.key);
NSLog(#"%#", snapshot.value);
NSLog(#"%#", self.distanceMutableArray);
}];
//Selected Languages Mutable Array
[[DataSource sharedInstance].selectedLanguageMutableArray removeAllObjects];
for (NSInteger i = 0; i < self.distanceMutableArray.count; i++) {
UserCustomizationData *item = [[UserCustomizationData alloc] init];
NSString* selectedUser = self.distanceMutableArray[i];
Firebase* selectedUserRef = [[DataSource sharedInstance].usersRef childByAppendingPath:selectedUser];
if (selectedUser.length > 0) {
Firebase* profilePicRef = [selectedUserRef childByAppendingPath:#"profilePicture"];
[profilePicRef observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
NSString* profPicString = snapshot.value;
NSData *dataFromBase64=[NSData base64DataFromString:profPicString];
UIImage *profPicImage = [[UIImage alloc]initWithData:dataFromBase64];
item.profilePicture = profPicImage;
}];
[[DataSource sharedInstance].selectedLanguageMutableArray addObject:item];
}
}
However, the for loop runs before the self.distanceMutableArray can populate. This throws everything off because the for loop relies on the self.distanceMutableArray being populated.
Is there a way to retrieve data such that the code will run fluidly and in the order that it is written?
The issue here is that Firebase works via asynchronous calls; your code will not work consistently because the code below the Firebase block may be called before the block completes.
You will need to start coding asynchronously and only perform actions on snapshot data after you know for sure it has been populated (inside the block)
[[languagesRef queryOrderedByValue] observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
//at this point, firebase has loaded the snapshot
// so its available to work with
[self.distanceMutableArray addObject:snapshot.key];
for (NSInteger i = 0; i < self.distanceMutableArray.count; i++) {
//do some stuff with the items in snapshot
}
}];
//don't work with anything that was from the snapshot as it may have not been filled yet
However there's an issue as the code is using childAdded, so that will iterate over each item in the firebase node, so that code won't work either as it won't load the array correctly (yes we can fix that by populating the array during each loop).
The additional challenge here is that you need to retrieve data from Firebase based on the result of your first snapshot. Again, same situation exists; only perform actions on that retrieved data after you know for sure it has been retrieved.
One solution is to load in the entire dataset at one time and iterate over it (by value instead of added). If your data sets are smaller that works. However, for big datasets that can be too much.
[[languagesRef queryOrderedByValue] observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
//at this point, firebase has loaded the snapshot
// so its available to work with and loaded with
//everything in the node
for ( FDataSnapshot *child in snapshot.children) {
NSDictionary *dict = child.value;
NSString *uid = child.key;
[self.distanceMutableArray addObject:uid];
}
// now the array is loaded do something with it
}];
Another option is to change how your data is stored in firebase so you can retrieve data together so you dont have to make multiple observe calls.

Trouble with Parse Relations

// Posts a message to the backend database
- (void)postMessage:(NSString *)message
{
// return right away if message is nil or all whitespace
if (!message || ![[message stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length]) {
return;
}
// Create new Message object and set relationships
PFObject *postedMessage = [PFObject objectWithClassName:#"Message"];
postedMessage[#"text"] = message;
PFRelation *tagRelation = [postedMessage relationForKey:#"tags"];
NSMutableArray *tags = [self generateTagsFromMessage:message];
NSLog(#"Number of Tags Found: %lu", (unsigned long)[tags count]);
[PFObject saveAllInBackground:tags block:^(BOOL succeeded, NSError *error) {
for (PFObject *tag in tags) {
// [tagRelation addObject:tag];
NSLog(#"%#", tag[#"text"]);
[tag setObject:postedMessage forKey:#"message"];
}
}];
[postedMessage saveInBackground]
}
I am using Parse as the back end for my application, but am having inexplicable difficulty creating a one-to-many relationship.
The idea is to make it so that users can post messages to the database with a tag on them, and the database stores Messages as one class, and Tags as another. The Message class has a "tags" field intended to store all tags in the message, and the Tag class has a "message" field intended to store the parent message of the tag. I thought I followed the tutorial of how to create a One-to-many relationship in Parse very well, but no matter what I cannot see the relationship on the Data Browser in Parse.
I have tried getting the relation to work by both simply setting the postedMessage as the newTag's message field, and also by creating a PFRelation * for the tags field of the message and adding tags to that. None of it works.
Does anybody know what my problem might be?
Thank you!
Edit
Specifically, the problem is that no relation is formed between the objects. By inspecting the data browser I can see that both the "tags" relational field of Message and "message" relational field of Tag remain blank after running this code.
An issue with your above code is that you're creating two new objects at once and triggering background saves on both at once.
For this to work, you need to save one side of the relationship first, then create the new objects, relate them and save them (in the block).
In your case you have a single message from multiple tags, so create the message and save it, then in the save block create the tags, add them to the message and re-save the message (it'll walk the tree looking for changes and save the new tags).
// Create new Message object
PFObject *postedMessage = [PFObject objectWithClassName:#"Message"];
postedMessage[#"text"] = message;
[postedMessage saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
// message saved, now create tags with link to message
PFRelation *tagRelation = [postedMessage relationForKey:#"tags"];
NSMutableArray *tags = [self generateTagsFromMessage:message];
NSLog(#"Number of Tags Found: %lu", (unsigned long)[tags count]);
for (PFObject *tag in tags) {
NSLog(#"%#", tag[#"text"]);
[tag setObject:postedMessage forKey:#"message"];
}
// now save the tags
[PFObject saveAllInBackground:tags block:^(BOOL succeeded, NSError *error) {
// now add the relationships
for (PFObject *tag in tags) {
[tagRelation addObject:tag];
}
// and save
[postedMessage saveInBackground];
}];
}];
Your code is almost correct, but you missed a few details. See my working version of your code re-written below. But first, read through the examples from Parse.
Take a look at this example from the Parse iOS documentation where they are setting the pointer relationship between two objects; a One-to-One relationship.
Neither object must have an objectId before creating the relationship.
// Create the post
PFObject *myPost = [PFObject objectWithClassName:#"Post"];
myPost[#"title"] = #"I'm Hungry";
myPost[#"content"] = #"Where should we go for lunch?";
// Create the comment
PFObject *myComment = [PFObject objectWithClassName:#"Comment"];
myComment[#"content"] = #"Let's do Sushirrito.";
// Add a relation between the Post and Comment
myComment[#"parent"] = myPost;
// This will save both myPost and myComment
[myComment saveInBackground];
What you are looking for is either a One-to-Many relationship array of pointers, which is outlined here and copied below for easy reference.
One object must have an objectId before creating the relationship
// Create Post
PFObject *newPost = [PFObject objectWithClassName:#"Post"];
// Set text content
[newPost setObject:[textView text] forKey:#"textContent"];
// Create relationship
[newPost setObject:[PFUser currentUser] forKey:#"author"];
// Save the new post
[newPost saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
// Dismiss the NewPostViewController and show the BlogTableViewController
[self dismissModalViewControllerAnimated:YES];
}
}];
OR you are looking for a One-to-Many relationship using the PFRelation object because; "you don't need to download all the Objects in a relation at once. This allows PFRelation to scale to many more objects than the NSArray of PFObject approach" . If this approach is what you are looking for, your re-written code below will work.
One object must have on objectId before creating the relationship
// Create new Message object and set relationships
PFObject *postedMessage = [PFObject objectWithClassName:#"TestClass"];
postedMessage[#"text"] = #"Hello World";
//example tags into array
PFObject *tag1 = [PFObject objectWithClassName:#"Tag"];
PFObject *tag2 = [PFObject objectWithClassName:#"Tag"];
PFObject *tag3 = [PFObject objectWithClassName:#"Tag"];
PFObject *tag4 = [PFObject objectWithClassName:#"Tag"];
NSArray *tags = [NSArray arrayWithObjects:tag1, tag2, tag3, tag4, nil];
//iterate the tags, add pointer for each
for (PFObject *tag in tags) {
//create a new tag-to-message pointer
[tag setObject:postedMessage forKey:#"message"];
}
//save all the tags and save the postedMessage
[PFObject saveAllInBackground:tags block:^(BOOL succeeded, NSError *error) {
if (!error) {
NSLog(#"created new postedMessage w/o relations :%#", postedMessage);
NSLog(#"saved tags with pointers :%#", tags);
PFRelation *tagRelation = [postedMessage relationForKey:#"tags"];
//iterate the tags, add relation for each
for (PFObject *tag in tags) {
//create a new postedMessage-to-tag relationship
[tagRelation addObject:tag];
}
//update the postedMessage
[postedMessage saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
NSLog(#"updated postedMessage with relations :%#", postedMessage);
}
}];
}
}];
Edit: Adding the code I wrote; which works as you would expect. Once you get your relationships working there is one thing you will encounter using the Browser and that is an "object not found" error when you click on an object relationship from an object that is not directly related. A better way to explain that is to refer you to my example below.
I created a Message-to-Notification-to-User relationship -- think of it in any direction you prefer. When I access this relationship data using the Browser in CoreData and I click on the Users notifications relations column, I am directed to the users notifications. However; when I click on any of the message relations column, I received the, "object not found" error message. This is expected behavior and logically it makes sense. The User object doesn't care what messages its notifications contain, much like how the messages don't care who the user is. When the User object calls delete on a notification, auto-cleanup removes the messages without the User object needing to worry about them. When an object has no reference pointers and auto-cleanup is called, the object will be removed.
- (void)addNotificationWithData:(NSDictionary*)userInfo withCompletionHandler:(void(^)(BOOL isFinished))completionHandler {
PFObject *notification = [PFObject objectWithClassName:#"Notification"];
PFObject *msg = [PFObject objectWithClassName:#"Message"];
[msg saveEventually:^(BOOL succeeded, NSError *error) {
if (!error) {
PFRelation *notification_to_message = [notification relationForKey:#"messages"];
[notification_to_message addObject:msg];
[notification saveEventually:^(BOOL succeeded, NSError *error) {
if (!error) {
PFRelation *user_to_notification = [[PFUser currentUser]relationForKey:#"notifications"];
[user_to_notification addObject:notification];
[[PFUser currentUser]saveEventually:^(BOOL succeeded, NSError *error) {
if (!error) {
//add the notication to the notification(s)
[notifications addObject:notification];
NSLog(#"the current notifications are :%#", notifications);
//call the completion handler
if (completionHandler) {
completionHandler(YES);
}
}
}];
}
}];
}
}];
}
You've commented out the addObject call to add the object to the relation. You'll need to uncomment that line in order to actually add the object to the relation.

Parse Custom cell show image according to boolean

I want to set ImageView hidden or not according to the boolean value true x false, which is located in Parse database. Could you give me some ideas how to do that? Got problems because of having property ImageView in my CustomCell.h
EDIT:
PFObject *yes = [PFObject objectWithClassName:#"MyClass"];
cell.discounts.hidden = [yes[#"yesnocolumn"] boolValue];
I just don`t understand to usage of PFObject, myclass returning (null) value.
EDIT2:
PFQuery *query = [PFQuery queryWithClassName:#"Classname"];
[query whereKey:#"yesnocolumn" equalTo:#1];
[query findObjectsInBackgroundWithBlock:^(NSArray *results, NSError *error) {
if (!error) {
cell.discounts.hidden = YES;
}else{
cell.discounts.hidden = NO;
}}];
The boolean from the service will look like either #0 or #1, so you can't just test for truthiness, because:
if (#0) {
// this condition is true. that's surprising!
}
So the safe way to test is to say:
cell.imageView.hidden = [myParseObject[#"hidden"] boolValue];
EDIT - Now I understand the misunderstanding. Here's how parse works:
1) Create an object locally:
PFObject *yes = [PFObject objectWithClassName:#"MyClass"];
This object won't have any values initialized. So any bools it contains will be 0, or NO.
2) Set some value locally:
yes[#"yesnocolumn"] = #1; // makes it true
// or = [NSNumber numberWithBool:YES];
3) Save it. (you can also use the data browser to initialize objects manually).
[yes saveInBackground];
4) Get an object. I think this is the part that's giving you trouble... just creating a local object doesn't really do much. The idea of parse is that objects are stored in the cloud. To get them, you need a PFQuery.
PFQuery *query = [PFQuery queryWithClassName:#"MyClass"];
[query findObjectsInBackgroundWithBlock:^(NSArray *results, NSError *error) {
// this will return all (up to 100 by default, MyClass objects that are
// saved in the cloud
if (!error && results.count) {
NSLog(#"the first object is %#", results[0]);
NSLog(#"the bool is %d", [results[0][#"yesnocolumn"] boolValue];
}
}];
Does this make sense? You can't expect the object to have any values initialized until you either set them locally, or retrieve already initialized remote copies. Also note, if you run the query on the next source line after saveInBackground, you won't get the saved result, since the save won't have finished. Start out saving in one run of your app, then reviewing in the data browser, then do a query.
It's worth doing a thorough review of their docs here.

How to Update n number of PFObjects with single PFQuery in IOS SDK

I am using Parse.com in IOS Application. In that i am using one Class Background which contains another Class values as row like an array.
I want to update an array of those values without using for loop. I want to update with only one Single PFQuery Call.
Class
Image -- id - image (PFFile) - count (integer)
Background -- id - imagesArr (Array)
The Background class contains image ids in imagesArr. I want to update one row in background. Then i need to update all images whose are imagesArr increment their count column in Image Class.
We can do it by using for loop.
like
[bgQuery findObjectinBackground:^(NSArray * imageIds, NSError *error)
{
if(!error)
{
for(int i= 0; i<imageIds.count; i++)
{
NSString *imageId = [imagesIds objectAtIndex:i];
PFQuery *getImageQuery = [PFquery queryWithClassName:#"Image"];
[getImageQuery getObjectWithIdInBackground:imageId withBlock]
// Code for refresh
}
}
}
We can do it like by using for . But i need to execute n number of PFQueries. I felt it leads to slow the application Performance.
Instead of this can we update all images in Background row imagesArr id with one single PFQuery.
Please help me in this issue.
Thanks in advnace.
This is a bit confusing because you keep saying that you are updating with a query. Queries are for retrieving objects.
With that being said, nested objects save automatically. For example
PFObject * object1 = [PFObject objectWithClassName:#"Object1"];
PFObject * object2 = [PFObject objectWithClassName:#"Object2"];
object1[#"object2ref"] = object2;
[object1 save]; // this should save object 2 as well.
If you do this several times and have an array of Object1's
[PFObject saveAllInBackground:arrayOfObject1objects]; // will save all object 1's, and object 2's
In retrieving objects.
PFQuery * query = [PFQuery queryWithClassName:#"Object1];
[query includeKey:#"object2Ref"]
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// all objects, and object2ref data should be available
}
else {
NSLog(#"Error, %# %#",error,[error userInfo]);
}
}];
Hope this helps, I'm not entirely sure what you're trying to do.

Extracting data from a Parse.com class and into an NSArray

I'm using the following query to get some data from a Parse.com class.
What I would like to do is extract the Rating object from the NSArray rateObjects. How would I go about doing this?
thanks for any help
PFQuery *rateQuery = [PFQuery queryWithClassName:#"Rating"];
[rateQuery whereKey:#"photo" equalTo:self.photo];
[rateQuery includeKey:#"photo"];
rateQuery.cachePolicy = kPFCachePolicyNetworkElseCache;
rateQuery.limit = 20;
[rateQuery findObjectsInBackgroundWithBlock:^(NSArray *rateObjects, NSError *error)
{
if( !error )
{
NSLog(#"rateObject %#", rateObjects);
}
}
];
Here's the NSLog output:
rateObject (
"<Rating:w9ENTO29mA:(null)> {\n ACL = \"<PFACL: 0x1e0a5380>\";\n Rating = 4;\n fromUser = \"<PFUser:uV2xu0c3ec>\";\n photo = \"<Photo:Rv4qqrHUPr>\";\n toUser = \"<PFUser:uV2xu0c3ec>\";\n user = \"<PFUser:uV2xu0c3ec>\";\n}",
"<Rating:t3pjtehYR0:(null)> {\n ACL = \"<PFACL: 0x1e0f9f90>\";\n Rating = 5;\n fromUser = \"<PFUser:uV2xu0c3ec>\";\n photo = \"<Photo:Rv4qqrHUPr>\";\n toUser = \"<PFUser:uV2xu0c3ec>\";\n user = \"<PFUser:uV2xu0c3ec>\";\n}"
)
Your NSArray will contain PFObjects, which you can treat in a similar way to a dictionary. In the query you ran above you received two rating objects back. If that's not what you wanted (you only wanted a single object) you may want to revisit how you're querying your data.
Assuming your Rating class in Parse contains a key called Rating you would access it like this:
[rateObject objectForKey:#"Rating"]
You can also use the new modern literal syntax if you like - rateObject[#"rating"]
You'll need to iterate through your array to view all the rating objects that have been returned, so you'll probably end up with something like this:
for (id item in rateObjects) {
int ratingVal = [[item objectForKey:#"Rating"] intValue];
NSLog(#"Rating: %d", ratingVal);
}
You may find Parse's iOS documentation helpful - and if you're not sure what the code above actually does, you may want to review arrays and how they work in Objective-C.
Try to use this:
[rateQuery findObjectsInBackgroundWithBlock:^(NSArray *rateObjects, NSError *error)
{
if( !error )
{
NSMutableArray *data = [[NSMutableArray alloc]init];
[data addObjectsFromArray:rateObjects];
NSArray *rating_data = [data valueForKey:#"Rating"];
NSLog(#"%#",[rating_data objectAtIndex:0]);
}
}
];
I hope this will help you.

Resources