Using RestKit with HTTP POST Operations
In my previous article on RestKit I showed how to use HTTP GET with JSON to access remote APIs like Twitter and BestBuy’s open APIs. That’s some really cool stuff, but now I want more. Now I want to be able to post more complex data than can be sent on the URL – I need to use POST. Sounds easy? Not! But that’s ok – we have restkit.org – and as you’ll see there’s some pretty sharp and involved people in this open-source project.
What’s the Goal?
A pretty common feature of most web sites and some mobile apps is some kind of way for a user to contact the company/webmaster/app owner. So I’d like to have one back-end for the millions of people that contact House of Beor about our wonderful products and how they would like to pay even more for them (fantasy). So that’s the goal: web and mobile software submitting the same data to the same back-end to process, and then the same “hey thanks” data coming back for display.
Where to Start
Read my earlier posts – or the short story is I’m working on iOS 6 iPads and using REST/JSON calls to my back-end server. A good place to start is of course with data objects for the user and for the response. Here’s what I’m going to use for the two objects:
Customer: @property (nonatomic, retain) NSString *firstName; @property (nonatomic, retain) NSString *lastName; @property (nonatomic, retain) NSString *country; @property (nonatomic, retain) NSString *state; @property (nonatomic, retain) NSString *zip; @property (nonatomic, retain) NSString *email; @property (nonatomic, retain) NSString *subject; @property (nonatomic, retain) NSString *message; |
Response: @property (nonatomic, retain) NSString *statusCode; @property (nonatomic, retain) NSString *contactId; @property (nonatomic, retain) NSString *message; @property (nonatomic, retain) NSString *autoResponse; @property (nonatomic, retain) NSString *autoResponseText; |
That should do ok for objects. As in my previous posts I’ll centralize most of my configuration for restkit, urls, service names, loggin levels, and such in a singleton class. I’ll also kickoff the RKObjectManager instance there as well. There is only one difference in this part of the code – and it is VERY important! Here’s my startup method now:
+ (void) startUp { [HOBSDKManager shared].appId = @"hob-mobile1"; [HOBSDKManager shared].hostName = @"https://myhost.com"; RKLogConfigureByName("RestKit/Network", RKLogLevelTrace); RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace); //Setup the HTTP client [AFNetworkActivityIndicatorManager sharedManager].enabled = YES; NSURL *baseURL = [NSURL URLWithString:[HOBSDKManager shared].hostName]; AFHTTPClient* client = [[AFHTTPClient alloc] initWithBaseURL:baseURL]; [client setDefaultHeader:@"Accept" value:RKMIMETypeJSON]; [HOBSDKManager shared].service= @"ContactUs"; // Initialize RestKit RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client]; objectManager.requestSerializationMIMEType = RKMIMETypeJSON; RKLogInfo(@"Configured RestKit Manager: %@", objectManager); }
Pretty much the same code as before – not the logging should be turned down for production, but it incredibly helpful and pretty interesting. You’ll notice that one bolded line – this is new. If you do not have this set then your JSON variables will be encoded as form parameters. Very tricky to figure out (thanks Blake!). Using the logging and debugging in Xcode are very important to figure things out like this. I knew what was wrong, but not how to fix it. Anyway…moving on.
Mapping Objects
In this example we need to map our customer object into JSON data. To do this we use RKObjectMapping, creating the mapping from an array of the key names for the attributes in our class:
RKObjectMapping *requestMapping = [RKObjectMapping requestMapping];
[requestMapping addAttributeMappingsFromArray:@[@"firstName", @"lastName",@"country",@"state",@"zip",@"email",@"subject",@"message"]];
Assign this to a RKRequestDescriptor and then assign that to the RKObjectManager:
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[Customer class] rootKeyPath:nil]; [[RKObjectManager sharedManager] addRequestDescriptor:requestDescriptor];
Ok – that’s the full path of data from our app, mapped into an object, and then assigned as a path thru the restkit framework to our endpoint. The next part is to configure the return path. The part is just like the previous example where we can map an object directly to a mapping class and restkit will handle all the rest for us:
RKObjectMapping *responseMapping = [RKObjectMapping mappingForClass:[ContactUsResponse class]]; [responseMapping addAttributeMappingsFromDictionary:@{ @"StatusCode": @"statusCode", @"contactId": @"contactId", @"Message": @"message", @"AutoResponse": @"autoResponse", @"AutoResponseText": @"autoResponseText" }]; RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:responseMapping pathPattern:nil keyPath:nil statusCodes:[NSIndexSet indexSetWithIndex:200]]; [[RKObjectManager sharedManager] addResponseDescriptor:responseDescriptor];
Same deal as before except we perform the mapping for the return JSON fields against our class property names. Then assign to a responseDescriptor and the RKObjectManager.
I did all of this in work in the AppDelegate.
Execution
Well that’s all cool – but does it work. First we’ll need a quickie GUI. I’ll use a quick viewcontroller with a plain jane set of fields. I won’t go into details on the form. Hit the button, extract the data into a customer object, and then call postObject:
[objectManager postObject:customer path:requestPath parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *result) { NSLog(@"We object mapped the response with the following result: %@", result); } failure:^(RKObjectRequestOperation *operation, NSError *error) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:[erro localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; NSLog(@"Hit error: %@", error); }];
The response will be in the result object (watch out for “erro” in the error block. Had to remove the “r” again). One thing to note the “request url” is the url path after the hostname that your end-point sits on. I had a tiny little error handling block and certainly you might want to do more with the response object.If you are getting unexplainable authentication errors check your SSL certs. I was using a dev box with self-signed certs and restkit by default will barf on that. You can add a preprocessor macro flag to Xcode’s build settings: _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_. Then you can use certs without a valid trust path.
That’s some pretty cool stuff. I had some struggles getting all this to work right, but the good news is that the core developers are pretty active and willing to help out. There are several key signs that tell me this is a great open source project to use: the people are helpful, they are refactoring out code, the unit test coverage is excellent, and logging is very good. I’ve only scratched the surface, but I’m going to keep using Restkit.
Sweet – that’s looking pretty good.
Thanks! Its pretty basic – I’ll be keeping at it to learn how to do some authentication stuff and also to learn about using more complex data structures and data relationships, but first I need to think of some more complex things to do. I’m thinking of making some “slicker” mobile apps for blogging and working with my blog. Not sure what really…just for kicks.