notify when data changes in parse sdk ios - ios

I am calling these to get all user list from class userlist in parse sdk ios
PFQuery *query = [PFQuery queryWithClassName:#"userlist"];
[query orderByAscending:kUserID];
query.cachePolicy = kPFCachePolicyNetworkElseCache;
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
{
if (!error)
{
for (PFObject *object in objects)
{
NSDictionary *dictUser = [NSDictionary dictionaryWithObjectsAndKeys:[object valueForKey:kUserName],kUserName,[object valueForKey:kUserID],kUserID,[object valueForKey:kUserImage],kUserImage,nil];
//NSLog(#"%#",dictUser);
[mutArrUserList addObject:dictUser];
}
}
else
{
NSLog(#"Error while getting data");
}
//NSLog(#"\nArray - %#",mutArrUserList);
[tblViewList reloadData];
}];
My question is how parse sdk ios can notify when data changes in class userlist?
Alternate solution is that, i have to continously make call to check by myself that this changes have been done to notify user!!!!!!

You can do this using the delegate pattern:
Creating a protocol like this:
#protocol myDelegate
#optional -(void)myCustomAction;
#end
With a property like this:
#property (nonatomic, strong) id<myDelegate> delegate;
After that you call you delegate method in your completion block, like this:
[delegate myCustomAction];
Then you need to implement that protocol in another class, just like you do with UITableViewDelegate.
Hope it helps.

If your asking in the parse can notify the app that something is updated on server, than the answer is no. Its your job to go checking for new data. If you dont want to go checking the data to many times then the you add NSDate attribute to parse object and when you want to update data check if the date is old enough to update. And every time you update the date from server also update this date to currenct date.
There is also an option to notify your app via silent push notifications, but that one is not recommended.
Or if your problem is how to get notify about data changes inside ios app, the delegates are the right way. If you'll be more specific about the problem I'll add some more code. Please explain a bit more.

Related

Separate API-class for Parse backend (IOS)

I am new with Parse and Ios-development.
I develop a ios-app that use Parse as backend.
I have got the main-function to work now, but i have a BIG problem.
I want to create a separate class for my API-handling to Parse. As i set it up now i have my parse-code directly in my view-controllers and as far as i know that not that nice coding.
But, the issue is to handle the background-jobs. Let say if i want to do a GET from the server, this can be done in a background-thread, just using "findObjectsInBackgroundWithBlock"
The problem is when i move this method to a separate API-class. Then my ViewController ask my API-class to get all the objects an the API-class will return it as soon its done. It will nor run in the background, i cant return a NSMutableArray with objects to the viewController until the fetch is done.
I have thinking that i maybe can get the data from parse synchronously in my API-class by using [query findObjects:&error] , as long as i figure out how to create my get-method in the API-class to run asynchronously.
I have try to create my API-method as a asynchronously method using blocks but will not run in background on a separate thread. (I am new to blocks an dont evan no if thats the correct way to crate a method that will run in a separate thread when using it)
Here is my API-method (Class: APIClient)
+ (void) GETAllShoppingGroups:(void (^) (NSMutableArray*))completionBlock{
//Create a mutable array (nil)
NSMutableArray *shoppingGroupsArray = nil;
//Create query for class ShoppingGroupe
PFQuery *query = [ShoppingGroupe query];
//Filter - find only the groups the current user is related to
[query whereKey:#"members" equalTo:[PFUser currentUser]];
//Sort Decending
[query orderByDescending:#"createdAt"];
//Tell Parse to also send the real member-objects and not only id
[query includeKey:#"members"];
//Send request of query to Parse with a "error-pointer"and fetch in a temp-array
NSError *error = nil;
NSArray *tempArray = [NSArray arrayWithArray:[query findObjects:&error]];
//Check for success
if (!tempArray) {
NSLog(#"%#", error);
NSLog(#"ERROR: %#", [error userInfo][#"error"]);
return completionBlock(shoppingGroupsArray);
} else {
//Seccess
shoppingGroupsArray = tempArray.mutableCopy;
completionBlock(shoppingGroupsArray);
}
}
Here is my ViewController Class (Class: ShoppingGruopViewController)
- (void) getAllObjects{
//Init array if nil
if (!self.shoppingGroupeArray) {
self.shoppingGroupeArray = [[NSMutableArray alloc]init];
}
//Remove old objects
[self.shoppingGroupeArray removeAllObjects];
//Get objects
[APIClient GETAllShoppingGroups:^(NSMutableArray* completionBlock){
if (completionBlock) {
[self.shoppingGroupeArray addObjectsFromArray:completionBlock]; }
[self.tableView reloadData];
}];
}

PFCloud callFunctionInBackground:withParameter:block: return value

In have a problem to Change CalUsed value I want to put Data in NSNumber *CalUsed1 to CalUsed for use in other function
#property (nonatomic, assign) NSNumber *CalUsed;
- (void) GetCalUsed{
PFUser *user = [PFUser currentUser];
[PFCloud callFunctionInBackground:#"CalUsed"
withParameters:#{#"user": user.objectId}
block:^(NSNumber *CalUsed1, NSError *error) {
if (!error) {
NSLog(#"Calories : %#",CalUsed1);
CalUsed = CalUsed1;
}
CalUsed = CalUsed1;
NSLog(#"TDEE IN FN is : %#",CalUsed);
}];
}
- (void) TestPrint{
NSLog(#"TDEE OUT FN : %#",CalUsed);
}
RESULT
TDEE OUT FN : (null)
Calories : 700
TDEE IN FN : 700
but I need global variable "CalUsed" change value to 700
Some Parse methods have a block in them. Because there will always be a delay in retrieving the data, there needs to be someone that the app "waits" until the data is retrieved. Unfortunately there is no easy way to do this so the block is there so anything that is within it will run AFTER the data is retrieved. If you put your NSLog in the block, it should work.
You have to remember that goal of any program is to execute the code as fast as possible. So when you call the Parse method, anything below it, NOT in the block will continue to run.
Update
If your going to use Parse you must understand the following (if you don't, there's no use in using Parse). Any parse functions that have a block will act differently than you would expect.
- (void) GetCalUsed{
PFUser *user = [PFUser currentUser];
[PFCloud callFunctionInBackground:#"CalUsed" //This is the Parse function
withParameters:#{#"user": user.objectId}
block:^(NSNumber *CalUsed1, NSError *error) { // This is where the block starts
if (!error) { //if the block retrieves the data with no problem, this will run
NSLog(#"Calories : %#",CalUsed1);
CalUsed = CalUsed1;
}
CalUsed = CalUsed1;
NSLog(#"TDEE IN FN is : %#",CalUsed);
}];
}
The thing about callFunctionInBackground is that there isn't really a way to return a value. So try to follow me: When the function is called, it will create a request to the Parse database to retrieve the data. But no matter how strong your connection, there will always be a delay (even if it is by milliseconds it still counts as a delay). Like I said before, the compiler wants to execute the code as fast as possible so this delay is going to cause problems. Using the above code, the Parse function will be called, however, as the app waits to retrieve the data, it's going to continue executing everything OUTSIDE of the Parse method. This is why your getting the null value (the NSLog outside of callFunctionInBackground is executed before the data is returned from Parse). To prevent this from happening, anything that involves calUsed1 or calUsed must go inside the block. Just like this:
- (void) GetCalUsed{
PFUser *user = [PFUser currentUser];
[PFCloud callFunctionInBackground:#"CalUsed"
withParameters:#{#"user": user.objectId}
block:^(NSNumber *CalUsed1, NSError *error) {
if (!error) {
NSLog(#"Calories : %#",CalUsed1);
CalUsed = CalUsed1;
//HERE YOU CAN MANIPULATE THE DATA HOWEVER YOU WISH. YOU CAN CALL A METHOD OR DO SOMETHING ELSE (aka, you can do whatever you want here)
}
}];
}
All in all, everything inside the block will be run only after the data from Parse has been retrieved...no matter how long it takes. If you still have questions feel free to ask.

Caching PFFile data from Parse

My app is a messaging style app and in it you can "tag" another user. (A bit like twitter).
Now, when this message is displayed, the avatar belonging to the person(s) who was tagged is displayed with that message.
The avatar of the user is stored as a PFFile against the PFUser object.
I'm loading it something like this...
PFImageView *parseImageView = ...
[taggedUser fetchIfNeededInBackgroundWithBlock:^(PFObject *user, NSError *error) {
parseImageView.file = user[#"avatar"];
[parseImageView loadInBackground];
}];
This all works fine.
The load if needed part of the code will most of the time not touch the network as for the majority of the time it has the user data cached.
However, the load in background part that gets the image and puts it into the image view runs every single time. There doesn't seem to be any caching on the PFFile data at all.
Even after downloading the same user's avatar numerous times it still goes to the network to get it.
Is there a way to get this data to cache or is this something I'll have to implement myself?
PFFile will automatically cache the file for you, if the previous PFQuery uses caching policy such as:
PFQuery *query = [PFQuery queryWithClassName:#"MyClass"];
query.cachePolicy = kPFCachePolicyCacheThenNetwork;
To check whether the PFFile is in local cache, use:
#property (assign, readonly) BOOL isDataAvailable
For example:
PFFile *file = [self.array objectForKey:#"File"];
if ([file isDataAvailable])
{
// no need to do query, it's already there
// you can use the cached file
} else
{
[file getDataInBackgroundWithBlock:^(NSData *data, NSError *error)
{
if (!error)
{
// use the newly retrieved data
}
}];
}
Hope it helps :)
In the end I created a singleton with an NSCache and queried this before going to Parse.
Works as a quick stop for now. Of course, it means that each new session has to download all the images again but it's a lot better now than it was.
You can cache result of PFQuery like below code..And need to check for cache without finding objects in background everytime..while retrieving the image.It has some other cache policies also..Please check attached link also..
PFQuery *attributesQuery = [PFQuery queryWithClassName:#"YourClassName"];
attributesQuery.cachePolicy = kPFCachePolicyCacheElseNetwork; //load cache if not then load network
if ([attributesQuery hasCachedResult]){
NSLog(#"hasCached result");
}else{
NSLog(#"noCached result");
}
Source:https://parse.com/questions/hascachedresult-always-returns-no
Hope it helps you....!

tabBarController sending delegate methods to tabs that don't respond to them

Using Storyboard I have setup an application with three tab bars. When I click on one of the tabs and have a singleton datasource class perform an action, and than immediately switch to another tab, when the singleton finishes fetching data externally it tries to send it to the current tab which causes a crash because the current tab I am on does not respond to the specific delegate method that I have implemented in the singleton delegate, and should not implement since there is no reason for that specific tab to perform that action. Here is how my delegate is currently setup.
#class DataHolder;
#protocol DataHolderControllerDelegate <NSObject>
#required
-(void)logout;
#optional
-(void)friendsQuarryDidFinishWithData;
-(void)pendingFriendsQuarryDidFinishWithData;
-(void)allUsersQuarryDidFinishWithData;
-(void)additionalFriendsFoundAndAdded;
-(void)messageQuarryFinishedWithData;
-(void)thumbnailQuaryDidFinishWithData;
-(void)sentRequestUsersFoundWithData;
#end
#interface DataHolder : NSObject;
#property (nonatomic,weak) id <DataHolderControllerDelegate>delegate;
The delegate is called within the method when data is queried.
-(void)messageQuarry{
PFQuery *messageQUery = [PFQuery queryWithClassName:#"Message"];
messageQUery.cachePolicy = kPFCachePolicyCacheThenNetwork;
[messageQUery whereKey:#"recipientIds" equalTo:[[PFUser currentUser] objectId]];
[messageQUery whereKey:#"file_type" equalTo:#"original_image.png"];
[messageQUery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error){
NSLog(#"ERROR: %#, %#", error, [error userInfo]);
}else{
messagesArray = [NSMutableArray arrayWithArray:objects];
[delegate messageQuarryFinishedWithData];
}
}];
}
Than, the view controller has a delegate method, this is the inboxViewController delegate method that is called when the delegate method above is called.
-(void)messageQuarryFinishedWithData{
self.messages = [NSMutableArray arrayWithArray:dataHolder.getMessages];
[self.tableView reloadData];
}
Remember that Delegates are meant to be intimate, meaning that its a 1-1 type relationship. It seems that what you are looking for is having a one-to-many relationship which is what NSNotificationCenter is used for. I recommend looking up the NSNotificationCenter documentation form Apple.

Parse.com app goes forward before i get the value I want (async) iOS

I use a query FindObjectsInBackgroundWithBlock (assync) to get a value from webservice... I want that value on the appdelegate before the app loads. I'm trying do it but he still load the app before check the webservices to get the value. How can I handle it? I already tried with a NSTimer..
I called this method in -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions :
-(void)checkUserVersion{
PFQuery *query = [PFQuery queryWithClassName:#"sqliteversion"]; //1
// NSNumber *n=databaseVersion;
[query whereKey:#"user_version" equalTo:[NSNumber numberWithInt:[databaseVersion integerValue]]];//2
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {//4
if (!error && [objects count]>0) {
for (PFObject *object in objects) {
valor1=[[object objectForKey:#"Value"]intValue];
user_version=[NSNumber numberWithInt:valor1];
}
}
}];
}
You may want to evaluate why it's necessary to have this information from Parse.com before launch. Also, it can mean many things to say "before launch", and there are many solutions that don't involve doing "everything" before the first view is attached to the window and shown.
That said...run the PFQuery fetch in the foreground. Doing this will block execution of the rest of your code, though. Instead of findObjectsInBackground just use the findObjects method of PFQuery. I would advise against doing this though, and instead try to find out a way to wait for the query to return while the app is already loaded. Maybe you could suspend user interaction but display a loading indicator or something?
You can't run any code in your app before it finishes launching. If the value is critical for your app to begin working you'll have to show a "waiting for data" message.

Resources