I am currently in the process of building a native Google Reader iPhone application similar to the successful application "Reeder for iPhone", however, with a full Twitter client inbuilt as well.
I have finished the Twitter client and am now struggling to start the Google Reader client. I've browsed through multiple documents and have taken a look at the gdata-objective-client samples, yet I still can't seem to understand what I have to do to accomplish the same functionality as Reeder does.
Basically I want to be able to present the user with a login screen. The user then submits their credentials and the access token and all of that are done behind scenes, like they do with Twitter's xAuth. I then want to push a view controller that shows a UITableView with all the current unread feeds. When the user clicks the UITableViewCell a detailed view is respectively pushed containing the posts content.
Is this possible and if so, how do I go about implementing these features? I would appreciate it if people posted "code snippets" and actually show how they achieve the implementations.
Thanks in advance!
EDIT: It has been brought to my attention that the google app engine isn't needed. The question however, still remains the same. How would I implement Google Reader into my application?
It was so simple. For all those wondering, to connect to Google Reader API, I did the following.
/* Google clientLogin API:
Content-type: application/x-www-form-urlencoded
Email=userName
Passwd=password
accountType=HOSTED_OR_GOOGLE
service=xapi
source = #"myComp-myApp-1.0"
*/
//define our return objects
BOOL authOK;
NSString *authMessage = [[NSString alloc] init];
NSArray *returnArray = nil;
//begin NSURLConnection prep:
NSMutableURLRequest *httpReq = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:GOOGLE_CLIENT_AUTH_URL] ];
[httpReq setTimeoutInterval:30.0];
//[httpReq setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[httpReq setHTTPMethod:#"POST"];
//set headers
[httpReq addValue:#"Content-Type" forHTTPHeaderField:#"application/x-www-form-urlencoded"];
//set post body
NSString *requestBody = [[NSString alloc]
initWithFormat:#"Email=%#&Passwd=%#&service=reader&accountType=HOSTED_OR_GOOGLE&source=%#",
gUserString, gPassString, [NSString stringWithFormat:#"%#%d", gSourceString]];
[httpReq setHTTPBody:[requestBody dataUsingEncoding:NSASCIIStringEncoding]];
NSHTTPURLResponse *response = nil;
NSError *error = nil;
NSData *data = nil;
NSString *responseStr = nil;
NSArray *responseLines = nil;
NSString *errorString;
//NSDictionary *dict;
int responseStatus = 0;
//this should be quick, and to keep same workflow, we'll do this sync.
//this should also get us by without messing with threads and run loops on Tiger.
data = [NSURLConnection sendSynchronousRequest:httpReq returningResponse:&response error:&error];
if ([data length] > 0) {
responseStr = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
//NSLog(#"Response From Google: %#", responseStr);
responseStatus = [response statusCode];
//dict = [[NSDictionary alloc] initWithDictionary:[response allHeaderFields]];
//if we got 200 authentication was successful
if (responseStatus == 200 ) {
authOK = TRUE;
authMessage = #"Successfully authenticated with Google. You can now start viewing your unread feeds.";
}
//403 = authentication failed.
else if (responseStatus == 403) {
authOK = FALSE;
//get Error code.
responseLines = [responseStr componentsSeparatedByString:#"\n"];
//find the line with the error string:
int i;
for (i =0; i < [responseLines count]; i++ ) {
if ([[responseLines objectAtIndex:i] rangeOfString:#"Error="].length != 0) {
errorString = [responseLines objectAtIndex:i] ;
}
}
errorString = [errorString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
/*
Official Google clientLogin Error Codes:
Error Code Description
BadAuthentication The login request used a username or password that is not recognized.
NotVerified The account email address has not been verified. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.
TermsNotAgreed The user has not agreed to terms. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.
CaptchaRequired A CAPTCHA is required. (A response with this error code will also contain an image URL and a CAPTCHA token.)
Unknown The error is unknown or unspecified; the request contained invalid input or was malformed.
AccountDeleted The user account has been deleted.
AccountDisabled The user account has been disabled.
ServiceDisabled The user's access to the specified service has been disabled. (The user account may still be valid.)
ServiceUnavailable The service is not available; try again later.
*/
if ([errorString rangeOfString:#"BadAuthentication" ].length != 0) {
authMessage = #"Please Check your Username and Password and try again.";
}else if ([errorString rangeOfString:#"NotVerified"].length != 0) {
authMessage = #"This account has not been verified. You will need to access your Google account directly to resolve this";
}else if ([errorString rangeOfString:#"TermsNotAgreed" ].length != 0) {
authMessage = #"You have not agreed to Google terms of use. You will need to access your Google account directly to resolve this";
}else if ([errorString rangeOfString:#"CaptchaRequired" ].length != 0) {
authMessage = #"Google is requiring a CAPTCHA response to continue. Please complete the CAPTCHA challenge in your browser, and try authenticating again";
//NSString *captchaURL = [responseStr substringFromIndex: [responseStr rangeOfString:#"CaptchaURL="].length];
//either open the standard URL in a browser, or show a custom sheet with the image and send it back...
//parse URL to append to GOOGLE_CAPTCHA_URL_PREFIX
//but for now... just launch the standard URL.
//[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:GOOGLE_CAPTCHA_STANDARD_UNLOCK_URL]];
}else if ([errorString rangeOfString:#"Unknown" ].length != 0) {
authMessage = #"An Unknow error has occurred; the request contained invalid input or was malformed.";
}else if ([errorString rangeOfString:#"AccountDeleted" ].length != 0) {
authMessage = #"This user account previously has been deleted.";
}else if ([errorString rangeOfString:#"AccountDisabled" ].length != 0) {
authMessage = #"This user account has been disabled.";
}else if ([errorString rangeOfString:#"ServiceDisabled" ].length != 0) {
authMessage = #"Your access to the specified service has been disabled. Please try again later.";
}else if ([errorString rangeOfString:#"ServiceUnavailable" ].length != 0) {
authMessage = #"The service is not available; please try again later.";
}
}//end 403 if
}
//check most likely: no internet connection error:
if (error != nil) {
authOK = FALSE;
if ( [error domain] == NSURLErrorDomain) {
authMessage = #"Could not reach Google.com. Please check your Internet Connection";
}else {
//other error
authMessage = [authMessage stringByAppendingFormat:#"Internal Error. Please contact notoptimal.net for further assistance. Error: %#", [error localizedDescription] ];
}
}
//NSLog (#"err localized description %#", [error localizedDescription]) ;
//NSLog (#"err localized failure reasons %#", [error localizedFailureReason]) ;
//NSLog(#"err code %d", [error code]) ;
//NSLog (#"err domain %#", [error domain]) ;
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Authentication" message:authMessage delegate:self cancelButtonTitle:#"Okay" otherButtonTitles:nil];
[alertView show];
[alertView release];
[gUserString release];
[gPassString release];
[gSourceString release];
[authMessage release];
}
}
Obviously I used my own delegates and such, but that is the overall want/feel that I brought to my application.
I'm currently working on pulling the unread feeds/items into a UITableView to display in my RootViewController. I'll update this with more information.
Thanks to all those that tried to help :D
THANK YOU. I knew there was a simple way to log in, but I was having a horrible time figuring it out. Btw for those of you who are copying/pasing the the0rkus's code above - you'll get a few errors. To test it out I added:
NSString *gUserString = #"yourlogin#gmail.com";
NSString *gPassString = #"yourpassword";
NSString *GOOGLE_CLIENT_AUTH_URL = #"https://www.google.com/accounts/ClientLogin?client=YourClient";
NSString *gSourceString = #"YourClient";
Google it, there are lots of blogs out there that describe how to use the Google Reader API. Here's a decent starting point:
http://mindsharestrategy.com/google-reader-api-a-brief-tutorial/
Related
I am using parse.com's backend and this is the code linked to my sign in button. The problem is that even if the user's username/password is wrong or the user doesn't enter one or the other, it will bring the user to the main screen and then it will say the error message.
Any idea why?
Thanks for the help in advance
- (IBAction)didTapLogin:(id)sender
{
NSString *username = usernameEntry.text;
NSString *password = passwordEntry.text;
if ((username.length != 4) && (password.length != 4))
{
[ProgressHUD show:#"Signing in..." Interaction:NO];
[PFUser logInWithUsernameInBackground:username password:password block:^(PFUser *user, NSError *error)
{
if (user != nil)
{
[ProgressHUD showSuccess:[NSString stringWithFormat:#"Welcome back %#!", [user objectForKey:PF_USER_FULLNAME]]];
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
else [ProgressHUD showError:[error.userInfo valueForKey:#"error"]];
}];
}
else [ProgressHUD showError:#"Please enter both username and password."];
}
Check if (error == nil) instead of if user is nil. The user can be non-nil if your app has enabled automatic user.
I am developing an iOS application in which I want to retrieve my facebook friends & send it to server for checking who are already using this app using their email or phone number. Once I get friends who are not using my application then I will show "Invite" button to send an email to their email address with app store link to download the app.
But as per facebook permissions , we can not retrieve the facebook friends email address.
Can anybody know how can I implement this feature in other way ?
Any kind of help is appreciated. Thanks.
you can not retrieve facebook friends email address but you can post on their wall whatever link you want to post i.e. app store link to download the app.
You can give a look to my this thread.
Since facebook does not provide email address so the idea behind this solution to offer is that, you can send invite friend request with the link to your application at AppStore. I have briefly described the steps in link to follow in order to accomplish this case.
Invitation message might look like this:
I would like you to try XYZ game. Here is the link for this
Application at AppStore:
Facebook iOS SDK - get friends list
You can use FB Graph API(/me/invitable_friends) as below for getting non app friends -
// m_invitableFriends - global array which will hold the list of invitable friends
- (void) getAllInvitableFriends
{
NSMutableArray *tempFriendsList = [[NSMutableArray alloc] init];
NSDictionary *limitParam = [NSDictionary dictionaryWithObjectsAndKeys:#"100", #"limit", nil];
[self getAllInvitableFriendsFromFB:limitParam addInList:tempFriendsList];
}
- (void) getAllInvitableFriendsFromFB:(NSDictionary*)parameters
addInList:(NSMutableArray *)tempFriendsList
{
[FBRequestConnection startWithGraphPath:#"/me/invitable_friends"
parameters:parameters
HTTPMethod:#"GET"
completionHandler:^(
FBRequestConnection *connection,
id result,
NSError *error
) {
NSLog(#"error=%#",error);
NSLog(#"result=%#",result);
NSArray *friendArray = [result objectForKey:#"data"];
[tempFriendsList addObjectsFromArray:friendArray];
NSDictionary *paging = [result objectForKey:#"paging"];
NSString *next = nil;
next = [paging objectForKey:#"next"];
if(next != nil)
{
NSDictionary *cursor = [paging objectForKey:#"cursors"];
NSString *after = [cursor objectForKey:#"after"];
//NSString *before = [cursor objectForKey:#"before"];
NSDictionary *limitParam = [NSDictionary dictionaryWithObjectsAndKeys:
#"100", #"limit", after, #"after"
, nil
];
[self getAllInvitableFriendsFromFB:limitParam addInList:tempFriendsList];
}
else
{
[self replaceGlobalListWithRecentData:tempFriendsList];
}
}];
}
- (void) replaceGlobalListWithRecentData:(NSMutableArray *)tempFriendsList
{
// replace global from received list
[m_invitableFriends removeAllObjects];
[m_invitableFriends addObjectsFromArray:tempFriendsList];
//NSLog(#"friendsList = %d", [m_invitableFriends count]);
[tempFriendsList release];
}
For Inviting non app friend -
you will get invite tokens with the list of friends returned by me/invitable_friends graph api. You can use these invite tokens with FBWebDialogs to send invite to friends as below
- (void) openFacebookFeedDialogForFriend:(NSString *)userInviteTokens {
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
userInviteTokens, #"to",
nil, #"object_id",
#"send", #"action_type",
actionLinksStr, #"actions",
nil];
[FBWebDialogs
presentRequestsDialogModallyWithSession:nil
message:#"Hi friend, I am playing game. Come and play this awesome game with me."
title:nil
parameters:params
handler:^(
FBWebDialogResult result,
NSURL *url,
NSError *error)
{
if (error) {
// Error launching the dialog or sending the request.
NSLog(#"Error sending request : %#", error.description);
}
else
{
if (result == FBWebDialogResultDialogNotCompleted)
{
// User clicked the "x" icon
NSLog(#"User canceled request.");
NSLog(#"Friend post dialog not complete, error: %#", error.description);
}
else
{
NSDictionary *resultParams = [g_mainApp->m_appDelegate parseURLParams:[url query]];
if (![resultParams valueForKey:#"request"])
{
// User clicked the Cancel button
NSLog(#"User canceled request.");
}
else
{
NSString *requestID = [resultParams valueForKey:#"request"];
// here you will get the fb id of the friend you invited,
// you can use this id to reward the sender when receiver accepts the request
NSLog(#"Feed post ID: %#", requestID);
NSLog(#"Friend post dialog complete: %#", url);
}
}
}
}];
}
Developing an app for iOS, I need to know how to have instanced and available an object created when user authenticates.
I am using OAuth2 method properly implementing gtm-oauth2 framework. The user entries, sees the login form displayed in a web view and correctly authenticates. In that moment, as detailed in the documentation, I go like this:
if (error != nil){
// Do whatever to control the error
}
else
{
// Authentication succeeded
// Assign the access token to the instance property for later use
self.accessToken = myAuth.accessToken;
[myAuth setShouldAuthorizeAllRequests:YES];
[self setAuth:myAuth];
// Display the access token to the user
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Authorization Succeeded"
message:[NSString stringWithFormat:#"Access Token: %#", myAuth.accessToken]
delegate:self
cancelButtonTitle:#"Dismiss"
otherButtonTitles:nil];
[alertView show];
}
Later, in the same controller, I use the self.auth object like this to access my API once the user has authenticated:
[request setURL:getCartsURL];
[request setValue:self.accessToken forHTTPHeaderField:#"Authorization"];
[self.auth authorizeRequest:request
completionHandler:^(NSError *error) {
NSString *output = nil;
if (error) {
output = [error description];
} else {
// Synchronous fetches like this are a really bad idea in Cocoa applications
//
// For a very easy async alternative, we could use GTMHTTPFetcher
NSURLResponse *response = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (data) {
// API fetch succeeded
output = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
SBJsonParser *jsonParser = [SBJsonParser new];
// Parse the JSON into an Object
id parsed = [jsonParser objectWithString:output];
NSArray *arrayResponse = [[NSArray alloc] initWithArray:parsed];
} else {
// fetch failed
output = [error description];
}
}
}];
So far, I have been using a local instance of self.auth object, what happens to be insufficient if I want to have that object globally accessed from any point of the whole app. Ok for the init view controller, but not for the whole app.
I think I can somehow access this first view controller to get the object anytime I want it. But I guess we have better methods to have it globally instanced and accessible from any point of the app.
Can you please help me with this?
Thanks a lot.
You should use a Singleton. Here is a nice article on how to set one up.
You could change the [self setAuth:myAuth]; of that ViewController to set an object on the AppDelegate. Create it there and set it, then you'll be able to access it from anywhere.
[[UIApplication sharedApplication] delegate] will give you a pointer to your app delegate, the one that was automatically created when you made the project.
Currently playing around with GooglePlusSample with scope:
#"https://www.googleapis.com/auth/plus.me",
#"https://www.googleapis.com/auth/userinfo.email" and
#"https://www.googleapis.com/auth/userinfo.profile".
Tried calling auth.userEmail, auth.userData in callback method finishedWithAuth:error:, but both are empty...
-(void)finishedWithAuth:(GTMOAuth2Authentication *)auth error:(NSError *)error{
NSLog(#"Received Error %# and auth object==%#",error,auth);
if (error) {
// Do some error handling here.
} else {
[self refreshInterfaceBasedOnSignIn];
NSLog(#"email %# ",[NSString stringWithFormat:#"Email: %#",[GPPSignIn sharedInstance].authentication.userEmail]);
NSLog(#"Received error %# and auth object %#",error, auth);
// 1. Create a |GTLServicePlus| instance to send a request to Google+.
GTLServicePlus* plusService = [[GTLServicePlus alloc] init] ;
plusService.retryEnabled = YES;
// 2. Set a valid |GTMOAuth2Authentication| object as the authorizer.
[plusService setAuthorizer:[GPPSignIn sharedInstance].authentication];
GTLQueryPlus *query = [GTLQueryPlus queryForPeopleGetWithUserId:#"me"];
// *4. Use the "v1" version of the Google+ API.*
plusService.apiVersion = #"v1";
[plusService executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
GTLPlusPerson *person,
NSError *error) {
if (error) {
//Handle Error
} else
{
NSLog(#"Email= %#",[GPPSignIn sharedInstance].authentication.userEmail);
NSLog(#"GoogleID=%#",person.identifier);
NSLog(#"User Name=%#",[person.name.givenName stringByAppendingFormat:#" %#",person.name.familyName]);
NSLog(#"Gender=%#",person.gender);
}
}];
}
}
Once user is authenticated you can call [[GPPSignIn sharedInstance] userEmail] to get authenticated user's email.
This worked for me :
Firstly use the userinfo.email scope as per :
signInButton.scope = [NSArray arrayWithObjects:
kGTLAuthScopePlusMe,
kGTLAuthScopePlusUserinfoEmail,
nil];
Then define these methods :
- (GTLServicePlus *)plusService {
static GTLServicePlus* service = nil;
if (!service) {
service = [[GTLServicePlus alloc] init];
// Have the service object set tickets to retry temporary error conditions
// automatically
service.retryEnabled = YES;
// Have the service object set tickets to automatically fetch additional
// pages of feeds when the feed's maxResult value is less than the number
// of items in the feed
service.shouldFetchNextPages = YES;
}
return service;
}
- (void)fetchUserProfile {
// Make a batch for fetching both the user's profile and the activity feed
GTLQueryPlus *profileQuery = [GTLQueryPlus queryForPeopleGetWithUserId:#"me"];
profileQuery.fields = #"id,emails,image,name,displayName";
profileQuery.completionBlock = ^(GTLServiceTicket *ticket, id object, NSError *error) {
if (error == nil) {
// Get the user profile
GTLPlusPerson *userProfile = object;
// Get what we want
NSArray * userEmails = userProfile.emails;
NSString * email = ((GTLPlusPersonEmailsItem *)[userEmails objectAtIndex:0]).value;
NSString * name = userProfile.displayName;
NSString * profileId = userProfile.identifier;
} else {
// Log the error
NSLog(#"Error : %#", [error localizedDescription]);
}
};
GTLBatchQuery *batchQuery = [GTLBatchQuery batchQuery];
[batchQuery addQuery:profileQuery];
GTLServicePlus *service = self.plusService;
self.profileTicket = [service executeQuery:batchQuery
completionHandler:^(GTLServiceTicket *ticket,
id result, NSError *error) {
self.profileTicket = nil;
// Update profile
}];
}
And finally call the "fetchUserProfile" method in the "finishedWithAuth" as per :
- (void)finishedWithAuth: (GTMOAuth2Authentication *)auth
error: (NSError *) error
{
// An error?
if (error != nil) {
// Log
} else {
// Set auth into the app delegate
myAppDelegate *appDelegate = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.auth = auth;
// Get user profile
self.plusService.authorizer = auth;
[self fetchUserProfile];
}
}
Note this may not be perfect as it's a 'work in progress', in particular re: getting the correct email address when the user has more than one but it's a start!
Good luck.
Steve
If you have Access not configured error check services in google api console. make sure you enable google plus api services.
I'm having some trouble pulling a Facebook access token from the web for a Facebook Feed App I'm writing. The problem isn't strictly related to gaining a Facebook token; this just frames the problem. When I go to https://graph.facebook.com/oauth/access_token?grant_type=client_credentials&client_id=[APP_ID]&client_secret=[APP_SECRET], I am returned a token on a page that simply says:
access_token=464483653570261|cY9NHFBWCDJ9hSQfswWFg0FDZvw
How can I parse that from the webpage into my app? I'm relatively new to Objective C (and I've only got a year of basic coding experience), so I tried to use part of a method that I found online to get a JSON feed, combined with a simple parsing method, but it didn't work. The code is as follows:
id getToken = [self objectWithUrl:[NSURL URLWithString:#"https://graph.facebook.com/
oauth/access_token?grant_type=client_credentials&
client_id=464483653570261&
client_secret=55bb8395ed0293bf37af695f6cdaa1fb"]];
NSString *fullToken = (NSString *)getToken;
NSLog(#"fullToken: %#", fullToken);
NSArray *components = [fullToken componentsSeparatedByString:#"="];
NSString *token = [components objectAtIndex:1];
NSLog(#"token: %#", token);
Both of my NSLogs say that the respective Strings point to (null). I'm not really certain what I'm doing wrong, and I haven't had much luck finding answers on the internet, as I'm not sure what to call what I'm trying to do. I'd appreciate any help, or alternate methods, that you might have.
By the look of it, the value you're getting isn't JSON, it's just a string.
Try something like this:
NSURL * url = [NSURL URLWithString:#"https://graph.facebook.com/
oauth/access_token?grant_type=client_credentials&
client_id=464483653570261&
client_secret=55bb8395ed0293bf37af695f6cdaa1fb"]];
NSString * fullToken = [NSString stringWithContentsOfUrl: url];
NSLog(#"fullToken: %#", fullToken);
NSArray *components = [fullToken componentsSeparatedByString:#"="];
NSString *token = [components objectAtIndex:1];
NSLog(#"token: %#", token);
There is a more simple way of getting User's access_token using the ACAccountStore & ACAccountType. Check the Full Code Below:
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
NSDictionary *FBOptions = [NSDictionary dictionaryWithObjectsAndKeys:FACEBOOK_APP_ID, ACFacebookAppIdKey,#[#"email"],ACFacebookPermissionsKey, nil];
[accountStore requestAccessToAccountsWithType:accountType options:FBOptions completion:
^(BOOL granted, NSError *error) {
if (granted) {
NSArray *facebookAccounts = [accountStore accountsWithAccountType:accountType];
FBAccount = [facebookAccounts firstObject];
NSLog(#"token :%#",[[FBAccount credential] oauthToken]);
} else {
NSLog(#"error getting permission %#",error);
if([error code]== ACErrorAccountNotFound){
NSLog(#"Account not found. Please setup your account in settings app");
}
else {
NSLog(#"Account access denied");
}
}
}];