Restkit Objective-C: Post request only works once - iphone

I'm using Restkit to communicate with Drupal CMS. When I'm sending the
first request everything works fine: I'm getting the correct JSON-
string -> awesome. This is what the console says:
2011-06-24 23:00:48.344 MyApp[1399:207] Sending POST request to URL
http://mysite.com/myproject/services/rest/service_views/get.json. HTTP
Body: view_name=viewsname
If the app tries to send the same request again, nothing happens. None
of the delegate-methods get called. The console says:
2011-06-24 23:03:40.224 MyApp[1399:207] Sending GET request to URL
http://www.mysite.com/myproject/services/rest/service_views/get.json.
HTTP Body:
I'm doing all the Restkit stuff in a special-class (singleton), which
I keep as an instance variable of my view-controller. In the init-function of this
class I am doing this:
RKObjectManager* objectManager = [RKObjectManager
objectManagerWithBaseURL:kBaseURLKey];
In my view-controller I'm calling a - (void)pollForNewData function,
which does the following:
RKObjectLoader* objectLoader = [[RKObjectManager sharedManager] loadObjectsAtResourcePath: kRessourceKey objectClass:[RKNotification class] delegate:self];
objectLoader.method = RKRequestMethodPOST;
objectLoader.params = [NSDictionary dictionaryWithKeysAndObjects: #"view_name", #"viewsname", nil];
[objectLoader send];
Can anybody help me? Do I have to do something special after the first
response came? Is it possible to cancel a request (if the current view was left)?

Current RestKit source (~0.9.2+) doesn't seem to have loadObjectsAtResourcePath:objectClass:delegate: method.
You could use something like this:
// It's for logging in Drupal's user resource with RestKit.
// You can change the user bits to your need.
RKObjectManager *objectManager = [RKObjectManager sharedManager];
RKObjectMapping *currentUserMapping = [objectManager.mappingProvider objectMappingForKeyPath:#"currentUser"];
RKObjectLoader *objectLoader = [objectManager objectLoaderWithResourcePath:#"user/login" delegate:self];
objectLoader.method = RKRequestMethodPOST;
objectLoader.objectMapping = currentUserMapping;
objectLoader.params = [NSDictionary dictionaryWithObjectsAndKeys:
#"testuser", #"username",
#"testpass", #"password",
nil];
[objectLoader send];
Hope it helps.

Related

Restkit will not insert object attributes to path pattern for request URL path

I'm trying to run a find by ID request on a REST API. I'm using RestKit 0.20. I have a Location object. It has an id attribute. I want to make a GET request to '/locations/:id' and receive the complete object as JSON.
I have the backend and it's working. I'm now trying to write the iOS client code.
Here's what I have:
RKObjectManager* m = [RKObjectManager sharedManager];
RKObjectMapping* lmap = [RKObjectMapping requestMapping];
[lmap addAttributeMappingsFromArray:#[#"id"]];
RKRequestDescriptor* req = [RKRequestDescriptor requestDescriptorWithMapping:lmap objectClass:[Location class] rootKeyPath:nil];
[m addRequestDescriptor:req];
Location* l = [[Location alloc] init];
l.id = [NSNumber numberWithInt:177];
[m getObject:l path:#"locations/:id" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"LOADED: %#", [mappingResult firstObject]);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"FAILED");
}];
After the code above is ran Restkit does not replace ':id: from the path with the ID attribute set in the Location object.
Do you guys have any ideas what I'm doing wrong?
UPDATE:
I had both request and response descriptors set for the Location class. I had a route added for the find_by_id request but it was a Named Route, not a Class Route. When I used the getObject:path:parameters:success:failure method the router did not fill in the 'id' placeholder (irregardless whether it was named 'id', 'object_id', 'identity' or whatever).
The solution I found is this:
Continue using a Named Route but use the getObjectsAtPathForRouteNamed:object:parameters:success:failure method instead
User a Class Route and continue using the getObject:path:parameters:success:failure method
The problem I was having was that when using a NamedRoute like so:
RKRoute * route = [RKRoute routeWithClass:className pathPattern:path method:RKRequestMethodFromString(method)];
[objectManager.router.routeSet addRoute:route];
and then querying for objects using the getObject:path:parameters:success:failure method did not cause the router to fill out any placeholders in the URL path.
You're using a request descriptor, but your aren't making a 'request' (PUT / POST). When doing a GET you need to use a response descriptor. Also, the mapping you're creating isn't specifying the class (so it's linked against NSDictionary. I'd usually use the response descriptor with a router too. Something like:
RKObjectManager* m = [RKObjectManager sharedManager];
RKObjectMapping* lmap = [RKObjectMapping mappingForClass:[Location class]];
[lmap addAttributeMappingsFromArray:#[#"identity"]];
RKResponseDescriptor* req = [RKResponseDescriptor responseDescriptorWithMapping:lmap pathPattern:#"locations/:identity" keyPath:nil statusCodes:[NSIndexSet indexSetWithIndex:200]];
[m addResponseDescriptor:req];
[m.router.routeSet addRoute:[RKRoute routeWithClass:[Location class] pathPattern:#"locations/:identity" method:RKRequestMethodGET]];
Location* l = [[Location alloc] init];
l.identity = [NSNumber numberWithInt:177];
[m getObject:l path:nil parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"LOADED: %#", [mappingResult array]);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"FAILED");
}];

Restkit MultiForm Post with an Image

Problem
I've been trying to post to the server with a multiform request that includes an image attachment. I haven't had trouble getting the image to the server, it is the other information that is not sending correctly.
Details
I'm using object mapping to configure several different attributes when receiving objects from the server:
//Using a custom class to map object I receive to
RKObjectMapping * memoryMapper = [RKObjectMapping mappingForClass:[MemoContent class]];
[memoryMapper mapAttributes:#"created", #"user", #"participants", #"tags", #"text", #"kind", #"video", #"location", nil];
[memoryMapper mapKeyPath:#"_id" toAttribute:#"memoryID"];
//MediaMapper handles the data needed for the Image attachments
RKObjectMapping * mediaMapper = [RKObjectMapping mappingForClass:[MemoMedia class]];
[mediaMapper mapKeyPath:#"processed" toAttribute:#"processed"];
[mediaMapper mapKeyPath:#"original" toAttribute:#"original"];
[mediaMapper mapKeyPath:#"mime" toAttribute:#"mimeType"];
[memoryMapper mapKeyPath:#"media" toRelationship:#"rawMedia" withMapping:mediaMapper];
//
[[RKObjectManager sharedManager].mappingProvider setMapping:memoryMapper forKeyPath:#"memories"];
[RKObjectManager sharedManager].serializationMIMEType = RKMIMETypeFormURLEncoded;
[RKObjectManager sharedManager].acceptMIMEType = RKMIMETypeJSON;
Then, when it comes time to post a photo I update configurations as follows:
RKObjectMapping * memoryMapper = [RKObjectMapping mappingForClass:[MemoContent class]];
[memoryMapper mapAttributes:#"created", #"participants", nil];
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:memoryMapper forClass:[MemoContent class]];
[[RKObjectManager sharedManager].mappingProvider setMapping:memoryMapper forKeyPath:#"memory"];
Participants are people tagged with the photo. Here is how I'm posting it, similar to this https://github.com/RestKit/RestKit/wiki/Attach-a-File-to-an-RKObjectLoader
[[RKObjectManager sharedManager] postObject:theMemory usingBlock:^(RKObjectLoader * loader){
RKObjectMapping* serializationMapping = [[[RKObjectManager sharedManager] mappingProvider] serializationMappingForClass:[MemoContent class]];
NSLog(#"serializationMapping: %#", serializationMapping);
loader.delegate = APP; //main app delegate posting, updating
NSError* error = nil;
RKObjectSerializer * serializer = [[RKObjectSerializer alloc] initWithObject:theMemory mapping:serializationMapping];
NSDictionary * dictionary = [serializer serializedObject:&error];
RKParams * params = [RKParams paramsWithDictionary:dictionary];
NSData * imageData = UIImagePNGRepresentation(theMemory.photo); //image data
[params setData:imageData MIMEType:#"image/png" forParam:#"attachment"];
loader.params = params;
loader.serializationMIMEType = RKMIMETypeFormURLEncoded;
}];
The server is receiving the image as planned, and actually does receive the 'created' and 'participants' unfortunately it's in a strange format that the server doesn't understand. It includes line breaks and such participants (\n 19843589323 \n created: \n 3-31-2012 00:00 (something like that, I will update when I have access to the logs.
I will give you any extra info you need. Would offer reputation for it if I had enough to do so ;)
In RestKit 0.20.0-pre3, RKObjectManager does have method multipartFormRequestWithObject:method:path:parameters:constructingBodyWithBlock:
An example of this task can be found at the RestKit Github page:
Article *article = [Article new];
UIImage *image = [UIImage imageNamed:#"some_image.png"];
// Serialize the Article attributes then attach a file
NSMutableURLRequest *request = [[RKObjectManager sharedManager] multipartFormRequestWithObject:article method:RKRequestMethodPOST path:nil parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:UIImagePNGRepresentation(image)
name:#"article[image]"
fileName:#"photo.png"
mimeType:#"image/png"];
}];
RKObjectRequestOperation *operation = [[RKObjectManager sharedManager] objectRequestOperationWithRequest:request success:nil failure:nil];
[[RKObjectManager sharedManager] enqueueObjectRequestOperation:operation]; // NOTE: Must be enqueued rather than started

RestKit GET query parameters

I've been using RestKit 0.10.0 for a while now and up until this point, I only posted serialized objects to my server:
[[RKObjectManager sharedManager] postObject:serializedObject
usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.objectMapping = responseMapping;
loader.serializationMIMEType = RKMIMETypeFormURLEncoded;
loader.targetObject = nil;
}];
So far, so good. But I now need to make a GET request to the server with a few query parameters. The first natural thing that came in mind was to do the same as I did for posting objects:
create a serialization mapping for the object encapsulating the query parameters
create a response mapping for the object being received from the server
define and use a router for RKRequestMethodGET (instead of RKRequestMethodPOST)
make the request using getObject:usingBlock (instead of postObject:usingBlock)
I soon found out this is not the way to do it, so after searching the available resources (RestKit Wiki, RestKit Google group) I now know of two solutions considered as valid:
Appending the query parameters to the resource path.
This works perfectly.
NSDictionary *queryParams = [NSDictionary dictionaryWithObjectsAndKeys:
token, #"accessToken",
[NSNumber numberWithInt:level], #"level",
[NSNumber numberWithInt:count], #"count",
nil];
NSString* resourcePath = [PEER_SUGGESTIONS_CONTROLLER_PATH stringByAppendingQueryParameters:queryParams];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:resourcePath
usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.objectMapping = responseMapping;
}];
Setting the query parameters in the loader block.
This does not send the query parameters.
RKParams *params = [RKParams params];
[params setValue:token forParam:#"accessToken"];
[params setValue:[NSNumber numberWithInt:level] forParam:#"level"];
[params setValue:[NSNumber numberWithInt:count] forParam:#"count"];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:PEER_SUGGESTIONS_CONTROLLER_PATH
usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.objectMapping = responseMapping;
loader.params = params;
}];
My questions are:
Why doesn't the second solution work?
Why is the first solution working without having to set the loader.targetObject to nil, although I do not have any root key path in the JSON response?
What are the cases where I should use the getObject:usingBlock method? What is its intended purpose?
What should I use loader.params for? The object mapping tutorial from the wiki says this property can be used to encapsulate POST parameters, but I do not see the point since I can wrap the parameters in the serialized object that is being sent with the method postObject:usingBlock.
Thanks.
[LATER EDIT]
Regarding the answer to my second question: I've been setting the targetObject to nil in the loader block when making POST requests beacause otherwise RestKit will try use the send object mapping for the response (check this link for a related discussion). But since I am using loadObjectsAtResourcePath:usingBlock:, there is no object being sent, therefore the response will naturally map on the response mapping without having to the set targetObject to nil.
Why doesn't the second solution work?
params is used to create a HTTP body, which is not used in a GET/HEAD request.
Why is the first solution working without having to set the loader.targetObject to nil, although I do not have any root key path
in the JSON response?
I think targetObject is nil by default. You normally don't set it, the request will create it if needed. The only time I use it is when requesting objects without primary keys or other weird problems.
What are the cases where I should use the getObject:usingBlock method? What is its intended purpose?
This is a convenience method so you don't have to remember all the correct syntax. Internally it just sends an object load request using GET.
EDIT:
Use this if you have an object you want to update.
What should I use loader.params for? The object mapping tutorial from the wiki says this property can be used to encapsulate POST
parameters, but I do not see the point since I can wrap the parameters
in the serialized object that is being sent with the method
postObject:usingBlock.
Whatever you put in params will be serialized to an HTTP body (or body stream). Again, postObject:usingBlock: is just a convenience method so you don't have to remember everything.
RestKit is open source. If you are not sure how it works you are free to follow the parameters internally. If you app and web service is well designed, you should be able to use the convenience methods. Sometimes you can not, and then you can use the raw forms like you have done.
EDIT:
Q Hrm, quoting your bullet points messed up the numbers...
I solved adding a Category to RKObjectLoader, that is:
for method
-(void)getObject:(id<NSObject>)object usingBlock:(RKObjectLoaderBlock)block;
I added into the Category a modified method:
-(void)getObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters usingBlock:(void(^)(RKObjectLoader *))block;
Here it is the listing fpr file "RKObjectManager+QueryParameters":
//
// RKObjectManager+QueryParameters.h
// AlphaClient
//
// Created by Antonio Rossi on 14/07/12.
//
#import <RestKit/RestKit.h>
#interface RKObjectManager (QueryParameters)
- (void)getObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters usingBlock:(void(^)(RKObjectLoader *))block;
- (void)sendObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters method:(RKRequestMethod)method usingBlock:(void(^)(RKObjectLoader *))block;
#end
Here is the listing for file "RKObjectManager+QueryParameters.m":
//
// RKObjectManager+QueryParameters.m
// AlphaClient
//
// Created by Antonio Rossi on 14/07/12.
//
#import "RKObjectManager+QueryParameters.h"
#implementation RKObjectManager (QueryParameters)
- (void)getObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters usingBlock:(void(^)(RKObjectLoader *loader))block {
[self sendObject:object queryParameters:queryParameters method:RKRequestMethodGET usingBlock:block];
}
- (void)sendObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters method:(RKRequestMethod)method usingBlock:(void(^)(RKObjectLoader *))block {
NSString *resourcePath = [self.router resourcePathForObject:object method:method];
[self sendObject:object toResourcePath:resourcePath usingBlock:^(RKObjectLoader *loader) {
loader.method = method;
// need to transform the original URL because when method is GET the additional paramentes don't get added
RKURL *originalBaseURL = [RKURL URLWithBaseURL:[loader.URL baseURL]];
NSString *resourcePath = [self.router resourcePathForObject:object method:RKRequestMethodGET];
RKURL *authTokenURL = [originalBaseURL URLByAppendingResourcePath:resourcePath queryParameters:queryParameters];
[loader setURL:authTokenURL];
block(loader);
}];
}
#end
One more step is to add #import "RKObjectManager+QueryParameters.h" in your implementation file.
In this new method it is assumed that the router property of RKObjectManager has been defined before making a call to it.

iOS RestKit how to send a request with a callback object (userData) set?

I'm trying to create a RestKit request to load an image from a web service and add it to a button as a background image. To do so asynchronously, I'm trying to add the button as a userData to the RKRequest object.
What I'm not sure of is how to send a fully configured RKRequest, I tried setting the delegate, calling prepareURLRequest and sendAsynchronously . The method that I expect to be called back does not get called.
- (void)didFinishLoad:(RKResponse*)response
To check that my request is properly configured, I sent it via a request queue, and that works.
What is the proper way to send fully configured requests with userData object using RestKit?
- (void)sendRequestForTrackerUserGravatar:(CoreDataButton*)coreDataButton {
/*This works, but does not allow the user data object to be set, meaning I don't know which button's image was loaded.
NSMutableDictionary* paramsDictionary = [[NSMutableDictionary alloc] init];
[paramsDictionary setValue:#"identicon" forKey:#"d"];
[[RKClient sharedClient] get:[self gravatarMD5HashWithUserID:trackerUser.user_id.intValue] queryParams:paramsDictionary delegate:self];
*/
//single request for a resource, includes a dictionary of parameters to be appended to the URL
// [paramsDictionary setValue:#"retro" forKey:#"d"];
User* user = (User*)coreDataButton.managedObject;
NSString* md5Hash = [self gravatarMD5HashWithUserID:user.user_id.intValue];
NSString* urlString = [NSString stringWithFormat:#"%#%#?d=identicon",kGravatarWebsite,md5Hash];
RKRequest* request = [[RKRequest alloc] initWithURL:[NSURL URLWithString:urlString] delegate:self];
request.userData = coreDataButton;
request.delegate = self;
request.cachePolicy = RKRequestCachePolicyEnabled;
//this works
//[[RKRequestQueue sharedQueue] addRequest:request];
//this does not seem to call back the delegate method
[request prepareURLRequest];
[request sendAsynchronously];
}
//request callback, does not get called
- (void)didFinishLoad:(RKResponse*)response
{
[self processResponse:response];
}
//queue callback, DOES get called
-(void)requestQueue:(RKRequestQueue *)queue didLoadResponse:(RKResponse *)response
{
[self processResponse:response];
}
This line:
[[RKClient sharedClient] get:[self gravatarMD5HashWithUserID:trackerUser.user_id.intValue] queryParams:paramsDictionary delegate:self];
actually returns the request that is created and then dispatched, so you can do:
RKRequest* r = [[RKClient sharedClient] get:[self gravatarMD5HashWithUserID:trackerUser.user_id.intValue] queryParams:paramsDictionary delegate:self];
[r setUserData:buttonReference];
and then catch the success with RKRequest delegate method:
- (void)request:(RKRequest *)request didLoadResponse:(RKResponse *)response {
// get button like this
UIButton* b = [request userData];
// process the response and get image
UIImage* i = [UIImage imageWithData:[self giveMeDataFromResponseNOW:response]]; //or however you handle responses
}

ResKit iOS - Application crash when i receive HTTP Error Code in RKObjectManager postObject method

i'm using RKObjectManager to make iOS RESTful requests to map responses to local objects. When i receive HTTP error codes like 400, RestKit sends data to RKObjectLoaderDelegate method:
- (void)objectLoaderDidLoadUnexpectedResponse:(RKObjectLoader *)objectLoader
but immediately afterwards the application crash without any reason.
The debugger doesn't give me any more information, where and why the application crashes.
To make the request
RKObjectManager * objectManager = [RKObjectManager objectManagerWithBaseURL:SERVER_BASE_URL];
RKDynamicRouter* router = [[RKDynamicRouter new] autorelease];
objectManager.router = router;
objectManager.format = RKMappingFormatJSON;
[router routeClass:[RKTUser class] toResourcePath:#"sessions" forMethod:RKRequestMethodPOST];
[[RKObjectManager sharedManager] postObject:user delegate:self];
Anyone with the same problem?
I just updated RestKit framework, and it start to work.