iOS: RestKit loadObject & send params - objective-c

using loadObjectAtResourcePath on GET method, doesn't include my parameters on the requests.
for example, if I send:
[RKObjectManager objectManagerWithBaseURL:#"http://something/ws"];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/res" delegate:self block:^(RKObjectLoader *loader) {
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
#"val", #"param1",
nil];
loader.params = [RKParams paramsWithDictionary:dict];
}];
the final url request doesn't include the "?param1=val" part - why?

Update Months Later
The real answer is that loader.params creates the HTTP BODY, hence it works for POST, PUT, DELETE etc but not for GET where the params are appended to the URL.
Hence, the answer below still works if you're facing the same issue for GET, but if you're sending out GET requests, it's mostly using methods that attach the params to the query string.
To summarize the differences between the two.
Sending params in the HTTP Body(i.e. POST, UPDATE, DELETE)
// Convert a NS Dictionary into Params
RKParams *params = [RKParams paramsWithDictionary:optionValues];
// I use sendObject to skip the router. Otherwise it's normally postObject
[[RKObjectManager sharedManager] sendObject:yourObject toResourcePath: yourResourcePath usingBlock:^(RKObjectLoader *loader) {
loader.method = RKRequestMethodPOST;
loader.delegate = delegate;
loader.params = params; // This sets params in the POST body and discards your yourObject mapping
} ];
Caveat Emptor (for above)
Setting params in the block destroys any mapping that you might have set in yourObject, kind of defeats the purpose of using object mapping. There's a fix here by Sebastian loader.params - Extra params if you really want to use this method to append extra parameters to your Post not in the object.
Sending in params as Query String (i.e. GET)
// Make a NS dictionary and use stringByAppendingQueryParameters
NSDictionary *shopParams = [NSDictionary dictionaryWithKeysAndObjects:
#"limit",#"20",
#"location",#"latitude,longitude",
nil];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/api/v1/shops.json" stringByAppendingQueryParameters:shopParams] delegate:objectDelegate];
The rest of the answer is just for reference, I'm a hoarder.
Old Answer
I'm using RestKit for my project and facing the same issue.
I think RKParams is mainly used to do POST requests. I cannot fully decipher your code because 1) I don't know loader's declaration? 2) RKParams is not to be used with Object Manager?
I did this.
Loader Method in App Delegate
NSDictionary *shopParams = [NSDictionary dictionaryWithKeysAndObjects:#"limit",#"30", nil];
[[RKClient sharedClient] get:#"/api/v1/shops.json" queryParams:shopParams delegate:self];
Delegate
- (void)requestDidStartLoad:(RKRequest *)request {
NSLog(#"RK Request description: %#",[request description]);
}
Output:
RK Request description: <RKRequest: 0x7993db0> and rails log say {"limit"=>"30"}.
From the autocomplete in Xcode, you can see the get request didn't even use RKParams. Just a NSDict. The POST requests uses it.
My goal is to attach a query string, i.e. ?location=singapore&etcetc to my API methods in Rails. For this, RK comes with a NSString addon called appendQueryParams RK docs link that you can use to append query params.
If your goal is POST images etc, you can follow the above line of thought of using RKClient.
Update:
If you just want to append parameters to Object Manager
NSDictionary *shopParams = [NSDictionary dictionaryWithKeysAndObjects:
#"limit",#"20",
#"location",#"latitude,longitude",
nil];
This is outdated and marked for deprecation.
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/api/v1/shops.json" appendQueryParams:shopParams] delegate:self];
Use this instead:
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/api/v1/shops.json stringByAppendingQueryParameters:shopParams] delegate:yourLoaderDelegate];
Rails Log: {"location"=>"latitude,longitude", "limit"=>"20"}
Hope in my answer I didn't make any wrong statements.
Refer to this question RestKit GET query parameters.

Related

RestKit PUT not working

I'm trying to do a fairly basic HTTP PUT using RestKit. I don't want to put the entire object, since the API call was designed to accept a single query parameter and just update that field. I've tried two approaches so far, both unsuccessful.
URL to post to: https://myserver/api/users/{userId}
Query string parameter: verificationCode=
Example usage: PUT https://myserver/api/users/101?verificationCode=646133
Approach #1: Put the query parameter in a RKParams object and make the PUT call with those params.
NSString *putUrl = [NSString stringWithFormat:#"/api/users/%i", [APIUserInfo sharedAPIUserInfo].apiUserIdx];
NSLog(#"the PUT url is %#", putUrl);
// Send a PUT to a remote resource. The dictionary will be transparently
// converted into a URL encoded representation and sent along as the request body
NSDictionary* paramsDict = [NSDictionary dictionaryWithObject:[_verificationCode text] forKey:#"verificationCode"];
// Convert the NS Dictionary into Params
RKParams *params = [RKParams paramsWithDictionary:paramsDict];
[[RKClient sharedClient] put:putUrl params:params delegate:self];
Approach #2: Build the entire url and try a PUT with params set to nil.
NSString *putUrl = [NSString stringWithFormat:#"/api/users/%i?verificationCode=%#", [APIUserInfo sharedAPIUserInfo].apiUserIdx, [_verificationCode text]];
NSLog(#"the PUT url is %#", putUrl);
[[RKClient sharedClient] put:putUrl params:nil delegate:self];
Neither approach is working for me. The first fails saying "RestKit was asked to retransmit a new body stream for a request. Possible connection error or authentication challenge?" then runs for about 10 seconds and times out. The second approach fails saying HTTP Status 405 - Method Not Allowed.
Can anyone point out where I'm going wrong, or provide me with a simple PUT example using RestKit? Most of the examples I've found at there are putting the entire object which I don't want to do in this case.
UPDATE:
Approach #2 worked well once I got a few things sorted out on the server side. Final solution:
NSString *putUrl = [NSString stringWithFormat:#"/api/users/verify/%i?verificationCode=%#", [APIUserInfo sharedAPIUserInfo].apiUserIdx, [_verificationCode text]];
NSLog(#"the PUT url is %#", putUrl);
[[RKClient sharedClient] put:putUrl params:nil delegate:self];
the HTTP PUT method is disabled on your webserver. It is by default on all webserver for security reasons.
HTTP Status 405 - Method Not Allowed.

Restkit, sending post data with loadObjectsAtResourcePath

If i am making a request with restkit i can use the shared client to send post parameters.
How can i do the same with shared Object manager, there seems to be no function to post data when objects are requested.
To re-iterate, am looking to send some post data when i am using loadObjectsAtResourcePath
Thanks
Must you use loadObjectsAtResourcePath? Here's what I use to send POST requests to my server
RKParams* params = [RKParams params];
[params setValue:#"The text" forParam:#"text"];
RKClient* myClient = [RKClient sharedClient];
[myClient post:resourceURL params:params delegate:self];
And you get your response back with
- (void)request:(RKRequest *)request didLoadResponse:(RKResponse *)response
PS: Just found this link: https://github.com/RestKit/RestKit/wiki/Posting-NSDictionary-as-JSON
You can use the block style object loader to customize the request:
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/monkeys.json" usingBlock:^(RKObjectLoader* loader) {
loader.objectMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[Monkey class]];
loader.method = RKRequestMethodPOST;
}];
https://github.com/RestKit/RestKit/blob/master/Code/ObjectMapping/RKObjectManager.h#L374

Mapping a JSON response to an object using RestKit and Objective-C

I am relatively new to Objective-C and am attempting to use RestKit to receive a JSON response from a web service. I have successfully received the data back to my application, which looks like this viewing the response:
{id:"1","Translation":"Test"}
I would like to map this translation to my "Translation" object in my application, but have tried a few different ways but am not sure how to achieve this.
So my questions are:
How can I map this response to my Translation object
Am I doing this correctly, creating a method to complete this call outwit my view controller?
My Translation Object
#implementation Translation
#synthesize identifier = _identifier;
#synthesize translation = _translation;
- (NSDictionary*)elementToPropertyMappings {
return [NSDictionary dictionaryWithKeysAndObjects:
#"id", #"identifier",
#"translation", #"translation",
nil];
}
#end
My Translate Method
- (NSString *)performTranslation:(NSString *)translation
{
NSString *data = [[NSString alloc] initWithFormat:#"{\"SourceId\": \"%#\",\"RegionTag\": \"%#\",\"InputString\": \"%#\"}", #"1", #"Glasgow", translation];
NSString *post = data;
RKRequest *MyRequest = [[RKRequest alloc] initWithURL:[[NSURL alloc] initWithString:#"http://my.url.com/Translation/Translate"]];
MyRequest.method = RKRequestMethodPOST;
MyRequest.HTTPBodyString = post;
MyRequest.additionalHTTPHeaders = [[NSDictionary alloc] initWithObjectsAndKeys:#"application/json", #"Content-Type", #"application/json", #"Accept", nil];
[MyRequest send];
RKResponse *Response = [MyRequest sendSynchronously];
return Response.bodyAsString; <--- looking to map this to translation object here
}
The snippet of your code seems a bit outdated. I strongly recommend reading the newest Object Mapping guide in order to leverage RestKit into it's fullest potential - especially the part Mapping without KVC.
Edit:
In order to post an object with RestKit and receive back an answer, we define a TranslationRequest class that will hold our request & Translation to hold our response.
Firstly, we set up our RKObjectManager and mappings (i usually do this in my AppDelegate):
RKObjectManager *manager = [RKObjectManager objectManagerWithBaseURL:kOurBaseUrl];
[manager setSerializationMIMEType:RKMIMETypeJSON];
//this is a singleton, but we keep the manager variable to avoid using [RKObjectManager sharedManager] all the time
//Here we define a mapping for the request. Note: We define it as a mapping from JSON to entity and use inverseMapping selector later.
RKObjectMapping *translationRequestMapping = [RKObjectMapping mappingForClass:[TranslationRequest class]];
[translationRequestMapping mapKeyPath:#"RegionTag" toAttribute:#"regionTag"];
...
[[manager mappingProvider] setSerializationMapping:[translationRequestMapping inverseMapping] forClass:[TranslationRequest class]];
//now we define the mapping for our response object
RKObjectMapping *translationMapping = [RKObjectMapping mappingForClass:[Translation class]];
[translationMapping mapKeyPath:#"id" toAttribute:#"identifier"];
[translationMapping mapKeyPath:#"Translation" toAttribute:#"translation"];
[[manager mappingProvider] addObjectMapping:mapping];
//finally, we route our TranslationRequest class to a given endpoint
[[manager router] routeClass:[TranslationRequest class] toResourcePath:kMyPostEndpoint];
This should be enough of the necessary setup. We can call our backend anywhere in the code (e.g. in any controller) like this:
//we create new TranslationRequest
TranslationRequest *request = [[TranslationRequest alloc] init];
[request setRegionTag:#"Hello"];
....
//then we fetch the desired mapping to map our response with
RKObjectMapping *responseMapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:class]
//and just call it. Be sure to let 'self' implement the required RKObjectManagerDelegate protocol
[[RKObjectManager sharedManager] postObject:request mapResponseWith:responseMapping delegate:self];]
Try this approach and let me know if you need any assistance.. I was not able to test it fully as i don't have any suitable backend that will return the responses, but judging from the RestKit log this should work.
You need to pass the returned JSON string into a JSON parser. I use SBJSON. You can then use the resulting dictionary to populate the properties of your object.
RestKit seems to have native objects that encapsulate four different JSON parsers. However, I'd advise caution because they seem to assume that the top level parsed object will always be a dictionary.
As another aside, the example in your question is not valid JSON. It should look like this:
{"id":"1","Translation":"Test"}

Send post request in XML format using RestKit

Hi I am using restkit for first time, and there are several questions that come to my mind. First when sending a post request using restkit what format is the request Json or XML and how can I specify it? I am sending a post request to the server to authenticate a user and should receive a conformation if details correct in XML format.
NSArray *objects = [NSArray arrayWithObjects: email, password, nil];
NSArray *keys = [NSArray arrayWithObjects:#"username", #"password", nil];
NSDictionary *params = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
[[RKClient sharedClient] post:#"/login" params:params delegate:self];
This is the code I am using, the xml accepted by the web services should look like
<login>
<username>user#example.com</username>
<password>Password</password>
</login>
It is sending the request,but I am not getting the right response. Is there a way to view what is the format of the request I am sending to the server ?
Sounds like you are using the wrong call. The post call you are using assumes that the rest service wants params like login=username&password=skdjgh, i.e. NOT in XML, but in 'normal REST format'. You need to either find a call to post a block of text using RestKit, or use another call. In other words you need to create the XML yourself (or use some library) then send that via a post.
Give this a try. I think it is supposed to do what you want. I never got it to work but I had other things wrong with my code.
RKObjectManager* objectManager = [RKObjectManager objectManagerWithBaseURL:#"http://mysite.com"];
objectManager.serializationMIMEType = RKMIMETypeXML;
Note: this may be what you do for sending XML back to you, not sending XML to the server. Don't know off the top of my head.
You can use RKRequestSerialization class to do the xml serialization for you.
Here is a code snippet from one of my projects:
#import <RestKit/RKRequestSerialization.h>
...
[RKObjectManager sharedManager].acceptMIMEType = RKMIMETypeTextXML;
NSString *loginData = #"<Login><UserId>test</UserId><Password>test</Password></Login>";
[[RKClient sharedClient] setValue:#"XYZ01" forHTTPHeaderField:#"ServiceId"];
[[RKClient sharedClient] post:#"/login"
params:[RKRequestSerialization serializationWithData:[loginDetails dataUsingEncoding:NSUTF8StringEncoding] MIMEType:RKMIMETypeTextXML]
delegate:self];
And, here is the dump using RKLogConfigureByName("RestKit/Network", RKLogLevelTrace)
2012-08-12 11:28:41.476 MyApp[730:707] T restkit.network:RKRequest.m:402 Prepared POST URLRequest '<NSMutableURLRequest https://someserver.com/MyApp/rest/service/request>'. HTTP Headers: {
Accept = "text/xml";
"Content-Length" = 75;
"Content-Type" = "text/xml";
ServiceId = XYZ01;
}. HTTP Body: <Login><UserId>test</UserId><Password>test</Password></Login>
I did objectManager.serializationMIMEType = RKMIMETypeXML; but the Content-Length of my request is 0, and the HTTP Body is empty. If I use RKMIMETypeJSON the request looks fine, but the xml serialization doesn't work. Any ideas why this happens? What else do I need to do to post xml with valid serialization?
I also found this in doc: "RestKit currently supports serialization to RKMIMETypeFormURLEncoded and RKMIMETypeJSON". So, how can I use RKObjectManager to post xml?
I solved this using ASIHTTPRequest and XMLWriter. Unfortunate, but RestKit just doesn't seem to support XML posts out the box.
I do use RK for GETting stuff though.

More than one RKObjectManager at a time (RestKit)

I am testing out RestKit and need to access different BaseUrls and also sometimes access a web service with the same baseUrl from different places "at once", lastly I also need to access the same baseUrl with different ressourcePaths in the same controller.
In my app delegate I set up the RKObjectManager singleton like this.
RKObjectManager *objectManager = [RKObjectManager objectManagerWithBaseURL:kBaseUrl];
[objectManager registerClass:[EntityClass1 class] forElementNamed:#"element1"];
[objectManager registerClass:[EntityClass2 class] forElementNamed:#"element2"];
.
.
.
etc.
The singleton approach is really easy to work with, I however can't figure out how to separate the different web service calls.
In MyViewController, which implement the RKObjectLoaderDelegate, I will have the two methods:
- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
//stuff with result
}
- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error {
//stuff with error
}
This causes no problems when MyViewController uses one RKObjectManager singleton to access one ressourcePath with one baseUrl.
If I start different requests in this way:
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:FLICKRPath delegate:self]
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:FOURSQUAREPath delegate:self]
and so on, within the same MyController, my problem is that FLICKRPath and FOURSQUAREPath of course has different baseUrl, but the RKObjectManager only has one?
If I get this working and can have different RKObjectManagers another problem arises.
The delegate methods didLoadObjects and didFailWithError will receive results from both RKObjectManagers and I can't see any other way to tell them apart than from their baseUrls. Potentially comparing each return value with a baseUrl and, even worse, a ressourcePath, in the delegate method does not appeal to me at all.
If I have different RKObjectManagers I guess I could pass them different delegates and build classes dedicated to deal with the return values from different baseUrls and ressourcePaths. This would mean I had to build yet another abstraction on top of MyController and RestKit, which also seems messy.
I have a strong feeling I am going about this in the wrong way, the RestKit source is very impressive which indicates that is me fighting the framework. I would really appreciate some best practice insights on the subject. I have been through all the resources and examples that I could find but have not seen the above use case. It is always one RKObjectManager, one baseUrl and one ressourcePath.
Thank you in advance.
Since there is no accepted answer yet: using multiple object managers is quite simple using RestKit.
From the Wiki (Using Multiple Base URLs (and Multiple Object Managers):
The first object manager you create will be the shared singleton
RestKit uses by default. But by creating additional object managers,
you can pull from their BaseURLs as needed, just be sure to retain
these new managers.
RKObjectManager *flickrManager =
[RKObjectManager objectManagerWithBaseURL:flickrBaseUrl]; // <-- shared singleton
RKObjectManager *foursquareManager =
[[RKObjectManager objectManagerWithBaseURL:foursquareBaseUrl] retain]; // <-- you must retain every other instance.
Depending on your application, you may want to put this second object
manager in a more accessible place, like a retained property on the
AppDelegate, so that it's easy to pull from as needed.
In the event that you need to differentiate between the results from
your two (or more) object managers, simply set an identifier in the
userData for the queries.
- (void)someAction(id)sender {
// .......
RKObjectLoader* loader = [[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/whatever" delegate:self];
loader.userData = #"foursquare";
// or do this, if you need a number instead of a string
loader.userData = [NSNumber numberWithInt:1234];
// .......
}
//Then when the delegate comes back you can cast it into a string or number as appropriate:
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
// .......
NSString* source = (NSString*) objectLoader.userData;
// or, if you did the NSNumber instead:
NSNumber* source = (NSNumber*) objectLoader.userData;
// .......
}
API change:
RKObjectLoader* loader = [[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"/whatever" delegate:self];
doesn't compile in RestKit v.0.10.3 (loadObjectsAtResourcePath:delegate: returns void). That method just wraps a few lines of code, though, so you can still get at the loader, and add userData, with the following:
RKObjectLoader *loader = [[RKObjectManager sharedManager] loaderWithResourcePath:resourcePath];
loader.userData = #"SOMEDATA";
loader.delegate = self;
loader.method = RKRequestMethodGET;
[loader send];
(adding note in case other new users run into the same issues I did).
And by the way, since userData property is also available on RKRequest, you can use the same approach for loading/identifying requests.
For example, some post request:
RKClient * client = [RKClient sharedClient];
[client post:#"/your-api-path" usingBlock:^(RKRequest *request) {
request.userData = #"<some-object-you-can-check-in-delegate-callback>";
request.params = someParamsForRequest;
request.delegate = <delegate you want to call when request is finished>;
}];
How about using objectLoader.
You'll find the mapped object type/Class objectLoader.objectMapping.objectClass and add your conditions based on it instead of the url
-(void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
// your condition based on -> objectLoader.objectMapping.objectClass
}
Hope it will help
Possible approach is to introduce one singletone for each base url.
You can instantiate as many RKObjectManager objects as you want. However, only the first one will become shared. Look into initWithHTTPClient: sources.
if (nil == sharedManager) {
[RKObjectManager setSharedManager:self];
}
We can't use default sharedManager method to target specific object manager but we can easily implement our own singleton. Here's an example for Google Maps object manager:
#implementation GMObjectManager
+ (GMObjectManager*)sharedManager
{
static GMObjectManager *manager; // keep reference
if (!manager) {
// init with custom base url
NSURL *baseUrl = [NSURL URLWithString:kGMBaseUrl];
manager = [GMObjectManager managerWithBaseURL:baseUrl];
}
return manager;
}
- (id)initWithHTTPClient:(AFHTTPClient *)client
{
self = [super initWithHTTPClient:client];
if (self) {
// additional initialization
}
return self;
}
#end
Usage:
CGObjectManager *googleMapsManager = [GMObjectManager sharedInstance];