Restkit MultiForm Post with an Image - iphone

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

Related

RestKit send post request, parse the result?

I'm trying to pull data from my web service API and parse the received JSON... I've been though RestKit tutorials, but I couldn't find any information on doing a post request!
Right now I have this code:
-(void) loadPerformers
{
// Create our request mapping
RKObjectMapping* requestMapping = [RKObjectMapping mappingForClass:[JsonOperationModel class]];
[requestMapping addAttributeMappingsFromArray:#[#"RequestType"]];
// Create our data mapping
RKObjectMapping* dataMapping = [RKObjectMapping mappingForClass:[DataModel class] ];
[dataMapping addAttributeMappingsFromArray:#[#"Status"]];
// Create our performer mapping
RKObjectMapping* performerMapping = [RKObjectMapping mappingForClass:[PerformerModel class] ];
[performerMapping addAttributeMappingsFromArray:#[#"IdPerformer", #"Name", #"Rate",
#"IsInWatch", #"Rating", #"PictureUrl", #"LastModifiedDate"]];
// Create our talent mapping
RKObjectMapping* talentMapping = [RKObjectMapping mappingForClass:[DataModel class] ];
[talentMapping addAttributeMappingsFromArray:#[#"Id", #"Value"]];
// Define the relationship mapping with request -> data
[requestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"data"
toKeyPath:#"data"
withMapping:dataMapping]];
// Define the relationship mapping with data -> performers
[requestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"Performers"
toKeyPath:#"Performers"
withMapping:performerMapping]];
// Define the relationship mapping with performer -> talent
[requestMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"Talents"
toKeyPath:#"Talents"
withMapping:talentMapping]];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:JsonOperationMapping pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
NSURL *URL = [NSURL URLWithString:#"http://10.10.5.106:8089/Mobile/Default.ashx"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
RKObjectRequestOperation *objectRequestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[ responseDescriptor]];
[objectRequestOperation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
RKLogInfo(#"result: %#", mappingResult.array);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
[objectRequestOperation start];
}
Which was a sample on an official tutorial of RestKit, modified to my needs according to my json. But how can I adapt this sample to send a JSON string to the URL, and then fetch the server anwser?
I couldn't find any information on that, and each of my server response needs some client information sent through a JSON string before being able to send a response back to the client. (It's isn't only fetching from an URL as my current sample does!)
Thanks for any input on this!

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");
}];

Include image using NSMutabaleDictionary with RKClient and receiving on the Php Server

1 - How can I include a picture using NSMutableDictionary with RestKit Client?
My server receives data from NSMutableDictionary in my code below.
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:usernameTextfield.text forKey:#"username"];
[client put:#"/main/insert" params:params delegate:self];
but how can I attach an image at the same time?
On the other hand, RKParams doesn't work for me on my server side.
RKParams *params=[[RKParams alloc]init];
imageData = UIImagePNGRepresentation(imageField.image);
[params setData:imageData MIMEType:#"image/png" forParam:#"image"];
Rest server code:
public function insert_put()
{
$username = $this->put('username');
$this->model->insertPost($username);
//Question number 2.
$data['success'] = 'added successfully';
$this->response($data, 200);
}
2 - How can I receive it from my server(php) ? I'm using put option to this.
I just do the following
- (void) upload: (UIImage *) pic onLoad:(RKObjectLoaderDidLoadObjectBlock) loadBlock onError:(RKRequestDidFailLoadWithErrorBlock)failBlock{
RKParams* imageParams = [RKParams params];
NSData* imageData = UIImageJPEGRepresentation(pic, 0.7f);
[imageParams setData:imageData MIMEType:#"image/jpg" forParam:#"FileUpload"];
NSString *resourcePath = #"/api/upload/";
RKObjectMapping *mapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[KFMedia class]];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:resourcePath usingBlock:^(RKObjectLoader *loader) {
loader.method = RKRequestMethodPOST;
loader.params = imageParams;
[self settingsForLoader:loader withMapping:mapping onLoad:loadBlock onError:failBlock];
}];
}
- (void) settingsForLoader: (RKObjectLoader *) loader withMapping: (RKObjectMapping *) mapping onLoad:(RKObjectLoaderDidLoadObjectBlock) loadBlock onError:(RKRequestDidFailLoadWithErrorBlock)failBlock{
loader.objectMapping = mapping;
loader.delegate = self;
loader.onDidLoadObject = loadBlock;
loader.onDidFailWithError = ^(NSError * error){
//NSLog(#"%#",error);
};
loader.onDidFailLoadWithError = failBlock;
loader.onDidLoadResponse = ^(RKResponse *response) {
[self fireErrorBlock:failBlock onErrorInResponse:response];
};
}
Your RestKit code looks fine, I think it is your php code that is failing. RestKit will upload the image as part of a multipart/form-data. This info will not appear in your normal _PUT array like your username did. I'm not sure what frameworks you are using (Is that the REST Controller library?), but you can access the file using _FILES['image'] or whatever your server framework uses. $this->files('image') maybe?
EDIT:
To create a mixed param file try this:
NSDictionary* strings = [NSDictionary dictionaryWithObjectsAndKeys: usernameTextfield.text, #"username", nil];
NSData* image = UIImagePNGRepresentation(imageView.image);
RKParams* params = [[RKParams alloc] initWithDictionary: strings];
[params setData: image MIMEType: #"image/png" withParameter: #"image"];
//And send it
[client put:#"/main/insert" params:params delegate:self];
But now you can not use $this->put, but instead $this->upload. For example:
$data = $this->upload->data();
$username = $data['username'];
$imagefile = $data['image'];

primaryKeyAttribute not working Restkit/Core Data

I just installed the framework restkit 0.9.3 and followed the Discussion Board example. Well, everything just worked great, however when I tried to use Core Data my User NSManagedObject class is duplicating even after declaring his primaryKeyAttribute (userID). For example, when I send a login request to my web-server, I return {"user":{"id":1, "username":"teste", ...}} .. but it seems to create a new row every time it invoques objectLoader:didLoadObjects.
User table:
Example code:
~ AppDelegate.m didFinishLaunching
RKManagedObjectMapping* userMapping = [RKManagedObjectMapping mappingForClass:[User class]];
userMapping.primaryKeyAttribute = #"userID";
userMapping.setDefaultValueForMissingAttributes = YES; // clear out any missing attributes (token on logout)
[userMapping mapKeyPathsToAttributes:
#"id", #"userID",
#"email", #"email",
#"username", #"username",
#"password", #"password",
nil];
[objectManager.mappingProvider registerMapping:userMapping withRootKeyPath:#"user"];
~ User.m loginWithDelegate
- (void)loginWithDelegate:(NSObject<UserAuthenticationDelegate>*)delegate {
_delegate = delegate;
[[RKObjectManager sharedManager] postObject:self delegate:self block:^(RKObjectLoader* loader) {
loader.resourcePath = #"/login";
loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) {
[mapping mapAttributes:#"username", #"password", nil];
}];
}];
}
~ User.m didLoadObjects (RKObjectLoaderDelegate)
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray *)objects {
if ([objectLoader wasSentToResourcePath:#"/login"]) {
[self loginWasSuccessful];
}
NSLog(#"number of user rows: %i", [User findAll].count);
}
What am I doing wrong?
Are you correctly implementing RKManagedObjectCache? For debugging I had it simply return nil and forgot about that. A little while later I found I had duplicates also.
The cache works by fetching local objects and comparing with server returned objects. Any local objects that are not in the server response will be deleted. In earlier versions it used a fetch request but in newer versions you must manually perform the request and return actual objects.
If you return nil, it thinks this object is not in your cache and will add a duplicate. Try implementing this method:
+ (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
For example:
+ (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext {
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity: entity];
[request setFetchLimit: 1];
[request setPredicate:[NSPredicate predicateWithFormat:#"%K = %#", primaryKeyAttribute, primaryKeyValue]];
NSArray *results = [NSManagedObject executeFetchRequest:request inContext: managedObjectContext];
if ([results count] == 0)
{
return nil;
}
return [results objectAtIndex:0];
}
I have found the problem related to targetObject (RKObjectLoader)
/**
* The target object to map results back onto. If nil, a new object instance
* for the appropriate mapping will be created. If not nil, the results will
* be used to update the targetObject's attributes and relationships.
*/
So when I set it to nil the postObject calls findOrCreateInstanceOfEntity:withPrimaryKeyAttribute:andValue
- (void)loginWithDelegate:(NSObject<UserAuthenticationDelegate>*)delegate {
_delegate = delegate;
[[RKObjectManager sharedManager] postObject:self delegate:self block:^(RKObjectLoader* loader) {
loader.resourcePath = #"/login";
loader.targetObject = nil;
loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) {
[mapping mapAttributes:#"username", #"password", nil];
}];
}];
}
As of the latest RESTKit version (0.23.2) you can define the primary key like this:
[_mapping addAttributeMappingsFromDictionary:#{ #"id" : #"objectId", #"name" : #"name" }];
[_mapping setIdentificationAttributes:#[ #"objectId" ]];
Whereas objectId is you primary key on the core data object.

Restkit Objective-C: Post request only works once

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.