I want to make an app that uses on the Google Reader API. But I'm finding out that there isn't an offical API for it - is there a problem using the unofficial API, in terms of App Store guidelines/approval? Would other apps (Reeder, etc) use this?
Also what is the best method for logging in? Is OAuth the preffered method? Is using Janrain a good idea?
Frankly Apple doesn't care if you use Google's unofficial API.
I worked for a customer on a RSS reader app that used Google Reader for syncing. We didn't use OAuth but the standard HTTP login which returns you a cookie where you'll have to extract a token from to use in consecutive calls to the various reader URLs.
I can post you the login code from my (old) proof of concept app.
It uses ASIHTTP and some custom string categories. The idea is to send a login request, get the response and extract the session ID/auth code from the response's cookie header. Then you can use that session ID/auth code for consecutive calls.
#pragma mark -
#pragma mark login
//this is your sessionID token you get from the login
//use this in consecutive calls to google reader
//this method returns you the header string you have to add to your request
//[request addRequestHeader: #"Cookie" value: [self sidHeader]];
- (NSString *) sidHeader
{
return [NSString stringWithFormat: #"SID=%#", [self sid]];
}
- (NSString *) authHeader
{
return [NSString stringWithFormat: #"GoogleLogin auth=%#",[self auth]];
}
//login to your google account and get the session ID
- (void) login
{
NSString *username = #"my.googlelogin#gmail.com";
NSString *password = #"mypassword123";
NSString *loginUrl = #"https://www.google.com/accounts/ClientLogin?client=NNW-Mac";
NSString *source = #"NNW-Mac"; //let's fake NetNewsWire
NSString *continueUrl = #"http://www.google.com";
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString: loginUrl]]; // log in & get cookies
[request addRequestHeader: #"User-Agent" value: #"NetNewsWire/3.2b25 (Mac OS X; http://www.newsgator.com/Individuals/NetNewsWire/)"];
[request setPostValue: username forKey: #"Email"];
[request setPostValue: password forKey: #"Passwd"];
[request setPostValue: #"reader" forKey: #"service"];
[request setPostValue: source forKey: #"source"];
[request setPostValue: continueUrl forKey: #"continue"];
[request setDelegate: self];
[request setDidFailSelector: #selector(loginRequestFailed:)];
[request setDidFinishSelector: #selector(loginRequestFinished:)];
[request start];
}
-(void)loginRequestFinished:(ASIHTTPRequest *)request
{
NSString *responseString = [request responseString];
//login failed
if ([responseString containsString: #"Error=BadAuthentication" ignoringCase: YES])
{
[self setLastError: [self errorWithDescription: #"Bad Username/Passsword" code: 0x001 andErrorLevel: 0x00]];
if ([delegate respondsToSelector: #selector(gReaderLoginDidFail:)])
{
[delegate gReaderLoginDidFail: self];
}
return NO;
}
//captcha required
if ([responseString containsString: #"CaptchaRequired" ignoringCase: YES])
{
[self setLastError: [self errorWithDescription: #"Captcha Required" code: 0x001 andErrorLevel: 0x00]];
if ([delegate respondsToSelector: #selector(gReaderLoginDidFail:)])
{
[delegate gReaderLoginDidFail: self];
}
return NO;
}
//extract SID + auth
NSArray *respArray = [responseString componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]];
NSString *sidString = [respArray objectAtIndex: 0];
sidString = [sidString stringByReplacingOccurrencesOfString: #"SID=" withString: #""];
[self setSid: sidString];
NSString *authString = [respArray objectAtIndex: 2];
authString = [authString stringByReplacingOccurrencesOfString: #"Auth=" withString: #""];
[self setAuth: authString];
//mesage delegate of success
if ([delegate respondsToSelector: #selector(gReaderLoginDidSucceed:)])
{
[delegate gReaderLoginDidSucceed: self];
}
return YES;
}
- (void)loginRequestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
//NSLog(#"login request failed with error: %#", [error localizedDescription]);
[self setLastError: error];
if ([delegate respondsToSelector: #selector(gReaderLoginDidFail:)])
{
[delegate gReaderLoginDidFail: self];
}
}
After login you can use sid and auth to forge requests to the Reader's API endpoints.
Example:
- (ASIHTTPRequest *) requestForAPIEndpoint: (NSString *) apiEndpoint
{
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString: apiEndpoint]];
[request addRequestHeader: #"User-Agent" value: #"NetNewsWire/3.2b25 (Mac OS X; http://www.newsgator.com/Individuals/NetNewsWire/)"];
[request addRequestHeader: #"Cookie" value: [self sidHeader]];
[request addRequestHeader: #"Authorization" value: [self authHeader]];
return request;
}
An interesting read about Google Reader and its private API is http://timbroder.com/2007/08/google-reader-api-functions.html
Please make sure to read the latest comments :)
/edit: I updated the code to use the auth header (which google introduced in june this year). I guess this would be the place to put your OAuth token in if you would use OAuth. guess
Ive since found this: "The Google Data APIs Objective-C Client Library provides an iPhone static library, a Mac OS X framework, and source code that make it easy to access data through Google Data APIs. " code.google.com/p/gdata-objectivec-client - which is great!
It doesn't include the Reader API however (because it's not been released).
I have been able to access the API by changing (in the OAuthSampleTouch example)
NSString *scope = #"http://www.google.com/m8/feeds/";
in OAuthSampleRootViewControllerTouch.m to
NSString *scope = #"http://www.google.com/reader/api/*";
and
urlStr = #"http://www.google.com/m8/feeds/contacts/default/thin";
to
urlStr = #"http://www.google.com/reader/atom/user/-/label/Design";
where Design is a folder name - check this http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI its a great help.
Update
I have since found that this technique to be the best / lightest / less-complicated :
Native Google Reader iPhone Application
Related
I've been looking around, saw similar posts, but nothing like this that could give me answers. This is my setup and flow of the app:
User has to login via Facebook, using Facebook Graph. LoginView is presented modally, non animated
When user logins I can retrieve FBID and I use this fbid to send it to my web service (REST)
Web service gets the FBID from the NSURL and matches it with database to retrieve other user info
Using JSONserialization i parse the JSON received from web service and display it in the view
PROBLEM: Everything returns NULL except FBID that I get from Facebook. BUT, if I logout from Facebook and then login, that's when it works.
Here is my code:
viewDidAppear method:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:NO];
if (FBSession.activeSession.isOpen) {
[self populateUserDetails];
}
//Connect to WebService
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://atnightcorp.com/api/member/id/%#/format/json", fbid]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection connectionWithRequest:request delegate:self];
NSArray *pics = [member valueForKeyPath:#"photos"];
NSString *picCount = [NSString stringWithFormat:#"%d", [pics count]];
[photosCount setTitle:picCount forState:UIControlStateNormal];
NSLog(#"PHOTO: %#", picCount);
NSLog(#"FB: %#", fbid);
}
I tried putting NSURL request and connection code in viewDidLoad, but then I don't get anything back.
My NSURLConnection methods:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
data = [[NSMutableData alloc]init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData
{
[data appendData:theData];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
member = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
UIAlertView *errorView = [[UIAlertView alloc]initWithTitle:#"Error" message:#"The download could not complete. Please make sure you are connected to internet" delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[errorView show];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
The populateUserDetails method that you have seen above:
- (void)populateUserDetails
{
if (FBSession.activeSession.isOpen) {
[[FBRequest requestForMe] startWithCompletionHandler:
^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *user,
NSError *error) {
if (!error) {
self.userProfileImage.profileID = user.id;
self.navigationItem.title = user.name;
self.fbid = user.id;
}
}];
}
}
This method basically sets the FBID once user is logged in. Other important things you should know that could help you understand my project:
FBID is set as NSString property in my .H file
All facebook connect thing goes on in AppDelegate
I need to dynamically set the NSURL after I find out who the user is.
if I manually input FBID in NSURL, then it works.
everything should be executed when user logins, I think that the timing of retrieving fbid and receiving data from web service is wrong but I can't get to fix it.
IF you need anything else, I will elaborate more and post more code if needed. -
PLEASE HELP as I've been looking for answers for last 3 days.
Your problem is that the populateUserDetails is called and it returns without waiting the code to be executed (because it's an async task with a completition handler, and when you call the NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://atnightcorp.com/api/member/id/%#/format/json", fbid]]; for the first time, the fbid is nuil or not set properly (also you should use self.fbid not fbid since fbid is a property).
So you should try to move the whole code that is handling the request from viewDidAppear into a separate method and you should call that method from startWithCompletionHandler after you set the line with self.fbid = user.id
Also call [super viewDidAppear:animated]; not with NO param (this won't solve your problem but this is the right way to do it)
I'm working on an app that asks the user to login to access some of his infrmations. I have a login.php file that is store on the server and all the usernames and passwords on the database.
On my app i have 2 uitextfields one for the username and one for the password. I understand that i can use the POST method to pass on the input to the web server and check wether the username and password match to then load the rest of the data. This is where am having trouble, can anyone help me with it, how do i pass on the inputs from the uitextfield to that web service to run the long.php script?
Skram's answer is correct, although you might be thinking what is ASIHTTPRequest?
You can get the framework here:
ASIHTTPrequest home
Here is a short beautiful tutorial on how to use it:
Awesome Tutorial
And here is some code I used to do a login some time ago:
-(IBAction)doLogin{
//make sure you have text in the username field, this is optional
if(![[username text] isEqualToString:#""]){
//URL of your web service
NSURL *url = [NSURL URLWithString:#"http://localhost:8888/myservice.php"];
//instantiate request object with URL
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
//Set post values before you send it off (forcing password to lowercase)
[request setPostValue:[[self.password text] lowercaseString] forKey:#"password"];
[request setPostValue:[username text] forKey:#"email"];
//this is optional, I have switch controlling what methods are called in the web service
[request setPostValue:#"2" forKey:#"method"];
//set the delegate to self for ASIHTTPRequest delegates (things you'll see in the tutorial)
[request setDelegate:self];
//send out request
[request startSynchronous];
//Now this code handles what happens after the web service call, notice I use a dictionary called user info to hold the response and I check the string to verify pass or fail and act accordingly.
NSString *verify = [NSString stringWithFormat:#"%#",[userinfo objectForKey:#"verify"]];
if([verify isEqualToString:#"pass"]){
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:
#"MainStoryboard_iPhone" bundle:[NSBundle mainBundle]];
UITabBarController *mainMenu =
[storyboard instantiateViewControllerWithIdentifier:#"mainMenu"];
[self.navigationController pushViewController:mainMenu animated:YES];
}
else{
//show login failed Dialog or something...
}
}}
Here some more code, this is what happens when the server returns a response.
- (void)requestFinished:(ASIHTTPRequest *)request{
if (request.responseStatusCode == 400) {
NSLog(#"Something is wrong");
} else if (request.responseStatusCode == 403) {
NSLog(#"Something is wrong");
} else if (request.responseStatusCode == 200) {
//the response code is good so proceed.
NSString *responseString = [request responseString];
userinfo = [responseString JSONValue];
NSLog(#"%#", [userinfo objectForKey:#"id"]);
NSLog(#"%#", [userinfo objectForKey:#"user"]);
NSLog(#"%#", [userinfo objectForKey:#"verify"]);
} else {
//print mystery code.
NSLog(#"%d",request.responseStatusCode);
}
}
Basically when you start the request using request startSynchronous it executes you server side code and returns a response string (or fails), you catch and handle the response string (or failure)in a delegate method you must implement from ASIHttpRequest -(void)requestFinished:(ASIHTTPRequest *)request. In the sample code I am parsing the response string into a JSON and then putting it into a dictionary for later use.
If you go through the tutorial this will all make sense to you very quickly. Hope it helps, good luck!
You would need to use a POST Request made to login.php Script on your server with a username and password value.
You can achieve this with NSURLConnection or a Framework such as ASIHTTPRequest's ASIFormDataRequest.
On your server you should do something like $_POST['username'] and $_POST['password'] to retrieve the values sent to your script to do the processing for your database.
EDIT: Basic example from iPhone Side with ASIHTTPRequest.
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:url]];
[request setPostValue:#"some_user" forKey:#"username"];
[request setPostValue:#"some_password" forKey:#"password"];
[request setDelegate:self];
[request startAsynchronous];
Implement the following method to catch the response:
-(void)requestFinished:(ASIHTTPRequest *)request {
NSLog(#"%#", [request responseString]);
}
How to proceed to next api request after successful authication in google auth2.0 in iPhone? I got return data with keys. I need to use calendar api and task api.
- (void)viewController:(GTMOAuth2ViewControllerTouch *)viewController
finishedWithAuth:(GTMOAuth2Authentication *)auth
error:(NSError *)error {
NSString *nextURLString = [NSString stringWithFormat:#"https://www.google.com/calendar/feeds/default/allcalendars/mymail#gmail.com"];
//i just gave this url for testing. dont know which url is required here.
NSURL *nextURL = [NSURL URLWithString:nextURLString];
NSURLRequest *request = [NSURLRequest requestWithURL:nextURL];
GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
if (error != nil) {
// Authentication failed
} else {
// Authentication succeeded
[fetcher setAuthorizer:auth];
}
}
I am using MGTwitterEngine+Oauth for twitter integration in iPhone.
I need to post status and status with media on twitter.When searching I came to know that it can be possible with REST API. https://dev.twitter.com/docs/api
But I did not find any links which can help how to use this api after I get authenticated with twitter.
So what I did I tried using Oauth but it requires manually entering the pin which is not accepted in my case.
So what I did it. I fetch information from mgtwitter engine methods:-
- (void) OAuthTwitterController: (SA_OAuthTwitterController *) controller authenticatedWithUsername: (NSString *) username {
NSLog(#"Inside authenticatedWithUsername");
NSLog(#"%#",[[_engine tokenOFAuth] key]);
NSLog(#"%#",[[_engine tokenOFAuth] pin]);
NSLog(#"%#",[[_engine tokenOFAuth] secret]);
[_engine getUserInformationFor:username];
}
tokenOFAuth is the instance of OAToken.
- (void)userInfoReceived:(NSArray *)userInfo forRequest:(NSString *)connectionIdentifier {
NSLog(#"User Info Received: %#", userInfo);
NSDictionary *userDict=[userInfo objectAtIndex:0];
oAuth.user_id=[userDict objectForKey:#"id"];
oAuth.screen_name=[userDict objectForKey:#"screen_name"];
oAuth.oauth_token=[[_engine tokenOFAuth]key];
oAuth.oauth_token_secret=[[_engine tokenOFAuth]secret];
[oAuth synchronousAuthorizeTwitterTokenWithVerifier:[[_engine tokenOFAuth]pin]];
oAuth.oauth_token_authorized=YES;
[oAuth saveOAuthTwitterContextToUserDefaults:oAuth];
[self sendTwitterTweetMethod];
}
-(void)sendTwitterTweetMethod
{
[oAuth loadOAuthTwitterContextFromUserDefaults];
NSString *postUrl = #"https://api.twitter.com/1/statuses/update.json";
ASIFormDataRequest *request = [[ASIFormDataRequest alloc] initWithURL:[NSURL URLWithString:postUrl]];
NSMutableDictionary *postInfo = [NSMutableDictionary dictionaryWithObject:#"I am testing my app" forKey:#"status"];
for (NSString *key in [postInfo allKeys]) {
[request setPostValue:[postInfo objectForKey:key] forKey:key];
}
[request addRequestHeader:#"Authorization" value:[oAuth oAuthHeaderForMethod:#"POST" andUrl:postUrl andParams:postInfo]];
NSLog(#"[oAuth oAuthHeaderForMethod:#"" andUrl:postUrl andParams:postInfo]=%#",[oAuth oAuthHeaderForMethod:#"POST" andUrl:postUrl andParams:postInfo]);
[request startSynchronous];
NSLog(#"Status posted. HTTP result code: %d", request.responseStatusCode);
[request release];
}
But it is giving response:-Status posted. HTTP result code: 401 which means unauthorized.
please suggest me to use this rest api with twitter+oauth and without manually entering pin also.
I am stuck here.Please help.
Any suggestions would be highly appreciated.
I am converting my app routines from ASIHTTP to AFNetworking due to the unfortunate discontinuation of work on that project ... and what I found out later to be the much better and smaller codebase of AFNetworking.
I am finding several issues. My code for ASIHTTPRequest is built as a method. This method takes a few parameters and posts the parameters to a url ... returning the resulting data. This data is always text, but in the interests of making a generic method, may sometimes be json, sometimes XML or sometimes HTML. Thus I built this method as a standalone generic URL downloader.
My issue is that when the routine is called I have to wait for a response. I know all the "synchronous is bad" arguments out there...and I don't do it a lot... but for some methods I want synchronous.
So, here is my question. My simplified ASIHTTP code is below, followed by the only way i could think of coding this in AFNetworking. The issue I have is that the AFNetworking sometimes does not for the response before returning from the method. The hint that #mattt gave of [operation waitUntilFinished] totally fails to hold the thread until the completion block is called... and my other method of [queue waitUntilAllOperationsAreFinished] does not necessarily always work either (and does NOT result in triggering the error portion of the [operation hasAcceptableStatusCode] clause). So, if anyone can help, WITHOUT The ever-present 'design it asynchronously', please do.
ASIHTTP version:
- (NSString *) queryChatSystem:(NSMutableDictionary *) theDict
{
NSString *response = [NSString stringWithString:#""];
NSString *theUrlString = [NSString stringWithFormat:#"%#%#",kDataDomain,kPathToChatScript];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:theUrlString]];
for (id key in theDict)
{
[request setPostValue:[theDict objectForKey:key] forKey:key];
}
[request setNumberOfTimesToRetryOnTimeout:3];
[request setAllowCompressedResponse:YES];
[request startSynchronous];
NSError *error = [request error];
if (! error)
{
response = [request responseString];
}
return response;
}
AFNetworking version
- (NSString *) af_queryChatSystem:(NSMutableDictionary *) theDict
{
NSMutableDictionary *theParams = [NSMutableDictionary dictionaryWithCapacity:1];
for (id key in theDict)
{
[theParams setObject:[theDict objectForKey:key] forKey:key];
}
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:kDataDomain]];
NSMutableURLRequest *theRequest = [httpClient requestWithMethod:#"POST" path:[NSString stringWithFormat:#"/%#",kPathToChatScript] parameters:theParams];
__block NSString *responseString = [NSString stringWithString:#""];
AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:theRequest] autorelease];
operation.completionBlock = ^ {
if ([operation hasAcceptableStatusCode]) {
responseString = [operation responseString];
NSLog(#"hasAcceptableStatusCode: %#",responseString);
}
else
{
NSLog(#"[Error]: (%# %#) %#", [operation.request HTTPMethod], [[operation.request URL] relativePath], operation.error);
}
};
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
[queue addOperation:operation];
[queue waitUntilAllOperationsAreFinished];
[httpClient release];
return responseString;
}
Thanks very much for any ideas.
- (void)af_queryChatSystem:(NSMutableDictionary *) theDict block:(void (^)(NSString *string))block {
...
}
Now within the completionBlock do:
block(operation.responseString);
block will act as the delegate for the operation. remove
-waitUntilAllOperationsAreFinished
and
return responseString
You call this like:
[YourInstance af_queryChatSystem:Dict block:^(NSString *string) {
// use string here
}];
Hope it helps. You can refer to the iOS example AFNetworking has
I strongly recommend to use this opportunity to convert to Apple's own NSURLConnection, rather than adopt yet another third party API. In this way you can be sure it won't be discontinued. I have found that the additional work required to get it to work is minimal - but it turns out to be much more robust and less error prone.
My solution is manually to run the current thread runloop until the callback have been processed.
Here is my code.
- (void)testRequest
{
MyHTTPClient* api = [MyHTTPClient sharedInstance]; // subclass of AFHTTPClient
NSDictionary* parameters = [NSDictionary dictionary]; // add query parameters to this dict.
__block int status = 0;
AFJSONRequestOperation* request = [api getPath:#"path/to/test"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject) {
// success code
status = 1;
NSLog(#"succeeded");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// failure
status = 2;
NSLog(#"failed");
}];
[api enqueueHTTPRequestOperation:request];
[api.operationQueue waitUntilAllOperationsAreFinished];
while (status == 0)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate date]];
}
STAssertEquals(status, 1, #"success block was executed");
}