Monday, June 20, 2011

iPhone Twitter oAuth

I've faced the problem of interaction with twitter from my iPhone application. oAuth makes life very complicated at first glance. But, actually, oAuth was not the problem. The problem is that all the libraries I tried to use were obsolete or buggy. Some of them have been created for a long time ago and didn't work, some of them are more fresh and show twitter login page but they have a javascript bug (or something like that), which does not allow to handle the login process properly.

The only tool which does work is ShareKit by Nate Weiner.

Of course, it requires some changes to be used just for twitter authorization, etc. Here is the code necessary to get authorized on twitter and get authorized user friends and user profile information.

I have created a new class based on SHKTwitter (see the code below). This is how to use it (somewhere in your controller).

In viewDidLoad:

_twitter = [[FTTwitter alloc] init];
_twitter.ftDelegate = self;


In viewDidAppear:

if ([_twitter isAuthorized]) {
NSString* screenname = [_twitter getAuthValueForKey:@"screen_name"];
[_twitter getFriends:screenname];
} else {
[_twitter promptAuthorization];
}


Delegate methods:

- (void)twitterGotFriends:(NSArray *)ids {
// save friends somewhere
}


The code which avoids extra calling, double calling, etc is skipped, of course.

File FTTwitterDelegate.h:

#import

@protocol FTTwitterDelegate

- (void)twitterGotFriends:(NSArray*)ids;

@end


File FTTwitter.h:

#import
#import "SHKTwitter.h"
#import "FTTwitterDelegate.h"

@interface FTTwitter : SHKTwitter {
id _ftDelegate;
}

@property (nonatomic, assign) id ftDelegate;

- (void)verifyCredentials;
- (void)verifyCredentialsTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data;
- (void)verifyCredentialsTicket:(OAServiceTicket *)ticket didFailWithError:(NSError*)error;

- (void)getFriends:(NSString*)screenname;
- (void)getFriendsTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data;
- (void)getFriendsTicket:(OAServiceTicket *)ticket didFailWithError:(NSError*)error;

@end


File FTTwitter.m:

#import "FTTwitter.h"
#import "JSON.h"

@implementation FTTwitter

@synthesize ftDelegate = _ftDelegate;

- (void)verifyCredentials {
OAMutableURLRequest *oRequest = [[OAMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://api.twitter.com/1/account/verify_credentials.json"]
consumer:consumer
token:accessToken
realm:nil
signatureProvider:nil];

[oRequest setHTTPMethod:@"GET"];

OAAsynchronousDataFetcher *fetcher = [OAAsynchronousDataFetcher asynchronousFetcherWithRequest:oRequest
delegate:self
didFinishSelector:@selector(verifyCredentialsTicket:didFinishWithData:)
didFailSelector:@selector(verifyCredentialsTicket:didFailWithError:)];

[fetcher start];
[oRequest release];
}

- (void)verifyCredentialsTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data {
// TODO better error handling here
NSString* json = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
ZZLOG(@"json: %@", json);
NSDictionary* response = [json JSONValue];
[SHK setAuthValue:[response objectForKey:@"screen_name"] forKey:@"screen_name" forSharer:[self sharerId]];

NSString* screen_name = [self getAuthValueForKey:@"screen_name"];

[[SHKActivityIndicator currentIndicator] displayActivity:SHKLocalizedString(@"Retrieving friends")];

[self getFriends:screen_name];
}

- (void)verifyCredentialsTicket:(OAServiceTicket *)ticket didFailWithError:(NSError*)error {
[[SHKActivityIndicator currentIndicator] hide];
ZZLOG(@"can't get user information");
}

- (void)tokenAccessTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data {
[super tokenAccessTicket:ticket didFinishWithData:data];

[[SHKActivityIndicator currentIndicator] displayActivity:SHKLocalizedString(@"Retrieving screenname")];
[self verifyCredentials];
}

- (void)getFriends:(NSString*)screenname {
OAMutableURLRequest *oRequest = [[OAMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://api.twitter.com/1/friends/ids.json"]
consumer:consumer
token:accessToken
realm:nil
signatureProvider:nil];
[oRequest setHTTPMethod:@"GET"];

OARequestParameter* p = [[OARequestParameter alloc] initWithName:@"screen_name" value:screenname];
[oRequest setParameters:[NSArray arrayWithObject:p]];

OAAsynchronousDataFetcher *fetcher = [OAAsynchronousDataFetcher asynchronousFetcherWithRequest:oRequest
delegate:self
didFinishSelector:@selector(getFriendsTicket:didFinishWithData:)
didFailSelector:@selector(getFriendsTicket:didFailWithError:)];

[fetcher start];
[oRequest release];
}

- (void)getFriendsTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data {
NSString* json = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
NSArray* ids = [json JSONValue];

// call delegate here!!!
if ([_ftDelegate respondsToSelector:@selector(twitterGotFriends:)]) {
[_ftDelegate twitterGotFriends:ids];
}

[[SHKActivityIndicator currentIndicator] hide];
}

- (void)getFriendsTicket:(OAServiceTicket *)ticket didFailWithError:(NSError*)error {
[[SHKActivityIndicator currentIndicator] hide];
}

@end