MagicalRecord import only saving last item - ios

I have an AFNetworking call that is calling my API and storing the results. I have a similar call that is working just fine this way. However for this one it seems to only be storing the last item.
[client getPath:#"GetItemsByFilter/" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Operation: %#", operation);
NSLog(#"Response: %#", responseObject);
NSArray *customerFieldResults = [responseObject valueForKeyPath:#"details.items"];
NSLog(#"Results: %#", customerFieldResults);
__block NSArray *array;
#try {
[CustomerFields MR_truncateAll];
array = [CustomerFields MR_importFromArray:customerFieldResults];
NSLog(#"done setting array: %#", array);
} #catch (NSException *e) {
NSLog(#"Exception: %#", e);
} #finally {
NSLog(#"tc done");
}
NSLog(#"Array 1: %#", array);
[[NSNotificationCenter defaultCenter] postNotificationName:kCustomerFieldSetComplete object:nil];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Operation: %#", operation);
NSError *jsonError;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:[[error localizedRecoverySuggestion] dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&jsonError];
NSLog(#"Error: %#", [error localizedRecoverySuggestion]);
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:#"Api Error: %#", [dict valueForKey:#"status"]] message:[dict valueForKey:#"statusMessage"] delegate:nil cancelButtonTitle:NSLocalizedString(#"Ok", #"Okay button") otherButtonTitles:nil];
[alert show];
}];
When I do it with a context, it doesn't even save the items. I am sending a notification when the receiving of this information is complete and in that notification I am displaying the results from a [CustomerFields findAll]. Only one item is found with a [CustomerFields findAll].
Array1 shows the complete list of items, but once I get back to the other controller it only returns the very last item that was in the array. Also if I wrap this in a saveWithBlock it will not see any items in the other controller.
Why is it only showing the last record from the import when I do a findAll?

I'm not too familiar with Magical Record, but perhaps you're not saving your core data context?
I assume that the method MR_importFromArray: inserts the records into CoreData?
If that's not the case, you perhaps want to loop through your results and create them one by one?
[client getPath:#"GetItemsByFilter/" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSArray *customerFieldResults = [responseObject valueForKeyPath:#"details.items"];
customerFieldResults = [CustomerFields MR_importFromArray:customerFieldResults];
//loop through array and create records
//save your core data context here
[[NSNotificationCenter defaultCenter] postNotificationName:kCustomerFieldSetComplete object:nil];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error){}];

I figured it out. It turns out the API I was using changed and the field I was using as the relatedByAttribute was coming back blank (a bug) so it was thinking all my records were duplicates and overwriting them.

Related

Getting JSON text did not start with array or object and option to allow fragments not set

Is there any way that I can print/NSLOG the string or the array that is being returned by the API in this following login code of mine:
-(void)loginToAPI:(NSString *)email password:(NSString *)password {
NSString *controller = #"login";
NSString *action = #"authenticate";
NSDictionary *params = #{#"username": email,
#"userpass": password,
#"controller": controller,
#"action": action,
#"app_id": APP_ID,
#"app_key": APP_KEY};
NSLog(#"params %#", params);
if ([self isNetworkAvailable]) {
AFHTTPRequestOperationManager *client = [AFHTTPRequestOperationManager manager];
client.responseSerializer.acceptableContentTypes = [client.responseSerializer.acceptableContentTypes setByAddingObject:#"text/html"];
[client POST:[[Config sharedInstance] getAPIURL]
parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSDictionary *jsonObject= responseObject;
NSString *status = [jsonObject objectForKey:#"status"];
NSLog(#"Request Successful, response '%#'", jsonObject);
if ([status isEqualToString:#"success"]) {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSDictionary *data = [jsonObject objectForKey:#"data"];
NSDictionary *userDict = [data objectForKey:#"user"];
NSDictionary *djsaDict = [data objectForKey:#"djsa"];
User *currentUser = [[User alloc] initWithProperties:userDict];
[currentUser save];
DJSA_Cutomer *djsa = [[DJSA_Cutomer alloc] initWithProperties:djsaDict];
NSData *userData = [NSKeyedArchiver archivedDataWithRootObject:currentUser];
NSData *djsaData = [NSKeyedArchiver archivedDataWithRootObject:djsa];
[userDefaults setObject:userData forKey:#"currentUser"];
[userDefaults setObject:djsaData forKey:#"djsa_customer"];
[userDefaults synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName: #"LOGIN_SUCCESS" object:currentUser userInfo:nil];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName: #"LOGIN_FAILED" object:nil userInfo:nil];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Request Failed with Error - loginToAPI: %#, %#", error, error.userInfo);
[[NSNotificationCenter defaultCenter] postNotificationName: #"SERVER_ERROR" object:nil userInfo:nil];
}];
}
}
It is always failing and going to the part "Request Failed with Error - loginToAPI". Is it possible to see the actual values returned by the server so I can diagnose the problem?
Thanks!
I had the same problem. The solution to it in my case was
client.responseSerializer = [AFHTTPResponseSerializer serializer];
I did that but I was still getting same error. The problem was that I was setting client.requestSerializer instead of client.responseSerializer. Small mistakes but takes lot of time.
You can get the status code off the response property from the operation object which should give you some idea of what went wrong:
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Request Failed with Error - loginToAPI: %#, %#", error, error.userInfo);
NSLog(#"%#", [NSHTTPURLResponse localized​String​For​Status​Code:operation.response.statusCode]);
[[NSNotificationCenter defaultCenter] postNotificationName: #"SERVER_ERROR" object:nil userInfo:nil];
}];
Otherwise, it's best to use a tool like Charles web proxy to inspect the actual request and response. It has a free trial which should be sufficient to do what you need, and is relatively easy to set up.
Replace this line:
client.responseSerializer.acceptableContentTypes = [client.responseSerializer.acceptableContentTypes setByAddingObject:#"text/html"];
with this:
[client.securityPolicy setValidatesDomainName:NO];
[client.securityPolicy setAllowInvalidCertificates:YES];
client.responseSerializer = [AFHTTPResponseSerializer serializer];

Returning from a block - Objective C

I have a class method containing a block, of AFNetworking in which i want to return one dictionary variable, code shown below:
+(NSMutableDictionary*) getFromUrl:(NSString*)url parametersPassed:(NSDictionary*)parameters;
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
__block NSMutableDictionary *resultarray;
[manager GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"JSON: %#", responseObject);
NSMutableDictionary *resultarrayTwo = (NSMutableDictionary *)responseObject;
resultarray = resultarrayTwo;
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"Error: %#, %#", error, operation.responseString);
UIAlertView *alertView=[[UIAlertView alloc] initWithTitle:#"Message" message:#"Try again" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alertView show];
}];
return resultArray;
}
How can i return resultArray here, it returns nothing here due to the difference in control flow.
I don't have much knowledge in Objective C block. Waiting for your help.
Thank you.
Change your function design to the following function using Completion Blocks
+(void)getFromUrl:(NSString*)url parametersPassed:(NSDictionary*)parameters completion:(void (^) (NSMutableArray *values))completion;
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"JSON: %#", responseObject);
NSMutableDictionary *resultarray = (NSMutableDictionary *)responseObject;
completion(resultarray);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"Error: %#, %#", error, operation.responseString);
UIAlertView *alertView=[[UIAlertView alloc] initWithTitle:#"Message" message:#"Try again" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alertView show];
completion(nil);
}];
}
And call the function
[YourClass getFromUrl:url parametersPassed:params completion:^(NSMutableArray *values) {
NSLog(#"%#",values);
}];
Update
Removed the extra array used.
Hope this helps.
The network call is asynchronous in nature, so having this method return its result isn't probably the right way of thinking as it implies you do a synchronous network call.
Have the method take a block parameter instead, and execute that block with the result at a later point of time from the AFNetworking completion block.
Before getting the response for GET call, it will execute the next lines. Thats why you are getting no data into Array.
You can call delegate method in success and failure block. This is the best solution.

AFNetworking 2.0 - how to pass response to another class on success from subclassed AFHTTPSessionManager

Beginner ios AFNetworking 2.0 Qns: Having subclassed AFHTTPSessionManager to something like "MyAPIManager" and placed my all my API calls (GET/POST/PUT etc.) in this custom manager class, I'm having problems making use of the response on request success in another class (say class B).
I know I can refactor this and pluck out the POST call portion to class B, so that I can dump the relevant class B methods in the callback, but this would get messy, especially with multiple API calls.
I want to pass this response (e.g. the returned objectId) to another class and right now I'm just using a NSNotification which class B listens for, but this still feels a bit 'hackish' and am wondering if there is a better way to do this.
Currently in MyAPIManager : AFHTTPSessionManager:
- (void) POSTRecordJson:(NSDictionary *)json
{
[self POST:#"classes/Record/" parameters:json success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"Posted JSON: %#", json.description);
if ([responseObject isKindOfClass:[NSDictionary class]]) {
NSLog(#"Response: %#", responseObject);
//Notify objectId received
[[NSNotificationCenter defaultCenter]
postNotificationName:#"ReceivedObjectIdNotification"
object:self
userInfo:responseObject];
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
And in Class B I've called:
MyApiManager *manager = [MyApiManager sharedInstance];
[manager POSTRecordJson:someJSONdict];
you could do 2 things.. by using a protocol/delegate or a block..
but i, personally, prefers block soo..
first make a block Datatype
typedef void(^SuccessBlock)(id success); example
and add the parameter with the block on it
- (void) POSTRecordJson:(NSDictionary *)json success:(SuccessBlock)success
{
[self POST:#"classes/Record/" parameters:json success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"Posted JSON: %#", json.description);
if ([responseObject isKindOfClass:[NSDictionary class]]) {
NSLog(#"Response: %#", responseObject);
//Notify objectId received
success(responseObject);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
and to call the new function..
MyApiManager *manager = [MyApiManager sharedInstance];
[manager POSTRecordJson:someJSONdict success:^(id result){
NSDictionary *dictionary = (NSDictionary *)result;
NSLog(#"response: %#",dictionary)
}];
You would want to pass a completion block into your -POSTRecordJson: method.
For example, you would refactor your method to do the following:
- (void) POSTRecordJson:(NSDictionary *)json completion:(void(^)(BOOL success, id response, NSError *error))completion
{
[self POST:#"classes/Record/" parameters:json success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"Posted JSON: %#", json.description);
if ([responseObject isKindOfClass:[NSDictionary class]])
{
NSLog(#"Response: %#", responseObject);
if (completion) //if completion is NULL, calling it will crash your app so we always check that it is present.
{
completion(YES, responseObject, nil);
}
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Error: %#", error);
if (completion)
{
completion(NO, nil, error);
}
}];
}
You could then handle this implementation like so:
//assuming `manager` and `dictionary` exist.
[manager POSTRecordJson:dictionary completion^(BOOL success, id response, NSError *error) {
if (success)
{
//do something with `response`
}
else
{
//do something with `error`
}
}];
However, if you are a beginner with AFNetworking and you want to adopt a great structure for handling web services, you should check out this excellent blog post.
You can use blocks to send the response back to the class after the response received from the server:
- (void) POSTRecordJson:(NSDictionary *)json response:(void (^)(id response, NSError *error))responseBlock
{
[self POST:#"classes/Record/" parameters:json success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"Posted JSON: %#", json.description);
if ([responseObject isKindOfClass:[NSDictionary class]]) {
NSLog(#"Response: %#", responseObject);
responseBlock(responseObject, nil);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Error: %#", error);
responseBlock(nil, error);
}];
}

AFNetworking download with progressive UIAlertView

I want to implement a UIAlertView which will show when [[AFHTTPRequestOperationManager manager] GET:] will start doing its job and will disappear automatically when the job is done. One cool extra feature would be if I could display UIProgressView with progress of AFHTTPRequestOperation.
For now I'm checking if a have anything in Core Data and based on that I initialize UIAlertView:
if (![self coreDataHasEntriesForEntityName:#"Group"]) {
downloadingAlert = [[UIAlertView alloc] initWithTitle:#"Pobieranie" message:#"Trwa pobieranie grup" delegate:nil cancelButtonTitle:#"Anuluj" otherButtonTitles:nil, nil];
[self collectData];
} else {
NSError *error;
[[self fetchedResultsController] performFetch:&error];
}
So to prevent from showing this alert when UITableView is already filled with data.
As you can see I'm calling this [self collectData] method which looks like:
-(void)collectData
{
[downloadingAlert show];
[[AFHTTPRequestOperationManager manager] GET:ALL_GROUPS parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSManagedObjectContext *tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
tempContext.parentContext = self.moc;
[tempContext performBlock:^{
// Doing something with responseObject
if (![tempContext save:&error]) {
NSLog(#"Couldn't save: %#", [error localizedDescription]);
}
[self.moc performBlock:^{
NSError *error;
if (![self.moc save:&error]) {
NSLog(#"Couldn't save: %#", [error localizedDescription]);
}
[downloadingAlert dismissWithClickedButtonIndex:0 animated:YES];
[self.writer performBlockAndWait:^{
NSError *error;
if (![self.writer save:&error]) {
NSLog(#"Couldn't save: %#", [error localizedDescription]);
}
}];
}];
}];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Show alert with info about failure
}];
}
As you can see I'm programatically showing this UIAlertView and dismissing it when downloading is completed and UITableView is reloaded. But I have no clue how to add UIProgressView or how to dismiss this UIAlertView without using dismissWithClickedButtonIndex:. Any ideas?
AFNetworking 2.0 HTTP POST Progress
You can receive progress information if you will use the following method
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromData:(NSData *)bodyData
progress:(NSProgress * __autoreleasing *)progress
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
of AFHTTPSessionManager class

AFNetworking and DELETE requests with Rails

I'm putting the finishing touches on my own version of the heroku rails mobile iOS photo sharing app. I have implemented and successfully sent POST and GET requests via HTTP on iOS. Unfortunately, the Heroku tutorial explicitly states it won;t be going into how to write the DELETE request. Here's what I have so far:
+ (void)deletePhoto:(NSString *)owner
image:(NSString *)recordId
block:(void (^)(Photo *, NSError *))block
{
NSMutableDictionary *mutableParameters = [NSMutableDictionary dictionary];
[mutableParameters setObject:recordId forKey:#"photo[id]"];
NSLog(#"Destroying %#", recordId);
[[CloudGlyphAPIClient sharedClient] deletePath:#"/photo" parameters:mutableParameters success:^(AFHTTPRequestOperation *operation, id JSON) {
if (block) {
NSLog(#"DELETE sussessful");
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (block) {
block(nil, error);
}
}];
}
+ (void)getPhotos:(NSString *)owner
block:(void (^)(NSSet *photos, NSError *error))block
{
NSMutableDictionary *mutableParameters = [NSMutableDictionary dictionary];
[mutableParameters setObject:owner forKey:#"photo[owner]"];
[[CloudGlyphAPIClient sharedClient] getPath:#"/photos" parameters:mutableParameters success:^(AFHTTPRequestOperation *operation, id JSON) {
NSMutableSet *mutablePhotos = [NSMutableSet set];
for (NSDictionary *attributes in [JSON valueForKeyPath:#"photos"]) {
Photo *photo = [[Photo alloc] initWithAttributes:attributes];
[mutablePhotos addObject:photo];
}
if (block) {
block([NSSet setWithSet:mutablePhotos], nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (block) {
block(nil, error);
}
}];
}
I've based the DELETE request off of the GET request, except in this case we are looking for a particular image user a certain owner. I get an error that the routes file doesn't contain a path for DELETE /photos... i added the destroy method to the rails app and raked the routes.rb file.
I feel like this is a rails GOTCHA somewhere.. thanks for your help ^_^
TL;DR trying to write DELETE request for a rails app with AFNetworking
In AFNetworking, the DELETE path is a url path like this: photos/18.json is the record marked for removal. Found the answer over here:
Answer
Effectively, one can concat together a string to the url of the ActiveREcord in a table.

Resources