For mobile, it’s all native – appcelerator vs phonegap vs native

I finally took enough time to form a stronger opinion in the html5 vs. native, vs Appcelerator, vs phone gap debate and for me, I’m all in on native.  Here’s a bit of a breakdown on the different choices.

Appcelerator

  • Pros: It’s main selling point is that it allows web developers to quickly create cross platform apps using html, javascript and css.  Appcelerator will actually compile down to native code which in theory runs as fast as native code written in objective C
  • Cons: Compiling to multiple platforms is a bit of a double edged sword.  It feels like they’re constantly having to make design decisions that support the lowest common denominator between the multitude of mobile platforms.  Furthermore, inherent in this model is a bit of follow mentality.  Any time a mobile platform introduces changes, they have to catch up to provide support.  I’ve heard anecdotes about developers experiencing performance hits when using appcelerator … but I actually think I’d call BS on that.  It’s fast.

Phone Gap

  • Pros: Phone gap allows web developers to write html, css, and javascript that runs within a mobile app.  So your web devs can write code quickly across multiple platforms.
  • Cons: IMO this is a cheap solution.  Unlike appcelerator, the javascript isn’t compiled into native code.  It’s simply embedded into a UIWebView (or other webkit control for other platforms).  This has important implications that you have to understand:
  1. UIWebView is a pig and has major performance issues.  Your app is NEVER gonna be as performant as a native app.
  2. You don’t have direct access to native controls like a UINavigationController or UIPopoverController, and native resources like the photo assets on the phone.  Instead, they’ve created some admittingly pretty awesome hooks which bridge the “gap” between native controls and javascript.  But these are a bit clunky from my experience.  It’s difficult to persist application state between these controls.  They’re also playing the same game as appcelerator is here – trying to support several cross platform controls.  They’re always playing catch up, and making lowest common denominator compromises.
HTML5
I’ve heard people argue for an HTML5 version of an app instead of native, but truthfully, I’m not sure these people really know what they’re talking about.  I guess they’re suggesting an html5 website that users browse to in their phones web browser.  But that’s got incredibly obvious shortcomings – Among others, no access to native controls or assets (obviously); no ability to receive push notifications; a very different look and feel; and having zero presence that persists on the users device.
Developing native applications across platforms is more expensive, no doubt.  But without native, far too many UI and UX compromises are made in a market that’s so dominated by solutions that really nail UI and UX.

 

 

Simplified NSUserDefaults

I use NSUserDefaults all the time for storing simple users settings and application state.  But there’s a whole lotta string literals and a bunch of repeating yourself that takes place.  That sucks ….

So I like to create a category on NSUserDefaults so I can treat it like a concrete class, whose API is enforced by the compiler and autocompleted by Xcode.  Here’s an example of how I use it to track a users email address within my app.  Note the rcast_ prefix.  Always a good idea to prefix your categories to prevent future name collisions.

//NSUserDefaults_RCastr.h
@interface NSUserDefaults (RCastr)

@property (assign, getter=rcast_userEmail,
           setter=rcast_setUserEmail:) NSString *rcast_userEmail;

//NSUserDefaults_RCastr.m
#import "NSUserDefaults+RCastr.h"
NSString *const rcastDefaultsKeyUserEmail = @"rcast_userEmail";

@implementation NSUserDefaults (RCastr)

#pragma mark -
#pragma mark Username

- (NSString *)rcast_userEmail {
    return [self stringForKey:rcastDefaultsKeyUserEmail];
}

- (void)rcast_setUserEmail:(NSString *)userEmail {
    [self setObject:userEmail forKey:rcastDefaultsKeyUserEmail];
    [self synchronize];
}

@end

That allows me to use user defaults like the following:


    //Set the email address
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    defaults.rcast_userEmail = @"user@domain.com";

    //Get the email address
       NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    if (defaults.rcast_userEmail){
        [FlurryAnalytics setUserID:defaults.rcast_userEmail];
    }

Dead easy change, but much cleaner and far less error prone.

oAuth authorization callback on the iPhone with WebView

There was a bit of chatter a while back about handling oAuth on the iPhone; in particular security balances sending a user to the website using Safari vs an embedded WebView.  Mike’s blog post discusses this so I won’t repeat, other than to say that I think either solution (webView or Safari) is open for spoofing.  An app developer could spoof an auth and grab creds in either case pretty easily … so I’ve chosen to optimize for user experience by implemented an oAuth webView for our app.

Here’s some of the relevant code for launching to an authorize website and handling the response.

* Our app uses oAuthConsumer library

* Our implimentation was against linkedin oauth which meant that I needed to pass the oauth_callback in our request token request by setting the oauth_callback parameter there rather than the authorize URL

In other words, our requestToken method looks something like the following:


OAMutableURLRequest                *request = [[[OAMutableURLRequest alloc] initWithURL: self.requestTokenURL consumer: self.consumer token:nil realm:nil signatureProvider: nil] autorelease];
 if (!request) return;
 [request setHTTPMethod: @"POST"];
 [request setParameters: [NSArray arrayWithObject: [[[OARequestParameter alloc] initWithName: @"oauth_callback" value: kLinkedInCallbackUrl] autorelease]]];
 OADataFetcher                *fetcher = [[[OADataFetcher alloc] init] autorelease];
 [fetcher fetchDataWithRequest: request delegate: self didFinishSelector: @selector(setRequestToken:withData:) didFailSelector: @selector(outhTicketFailed:data:)];

With the request token set, we’re able to pass this to the authorize url and it will return to our custom url

/*Create the request object for the linkedin URL*/


if (!_requestToken.key && _requestToken.secret) return nil;    // we need a valid request token to generate the URL

 OAMutableURLRequest            *request = [[[OAMutableURLRequest alloc] initWithURL: self.authorizeURL consumer: nil token: _requestToken realm: nil signatureProvider: nil] autorelease];

 [request setParameters: [NSArray arrayWithObject: [[[OARequestParameter alloc] initWithName: @"oauth_token" value: _requestToken.key] autorelease]]];

/*Load the webview with this request*/


[_webView loadRequest: request];

/*Check the response for a custom uri and redirect if it's set*/
- (BOOL) webView: (UIWebView *) webView shouldStartLoadWithRequest: (NSURLRequest *) request navigationType: (UIWebViewNavigationType) navigationType {
 NSData                *data = [request HTTPBody];
 char                *raw = data ? (char *) [data bytes] : "";
 NSURL* url = request.URL;

 if ([url.scheme isEqualToString:@"liconnect"]) {
 [_spinner stopAnimating];
 if ([url.resourceSpecifier isEqualToString:@"cancel"]) {
 if ([_delegate respondsToSelector:@selector(OAuthLinkedInAuthorizeViewFailed:)]) {
 [_delegate OAuthLinkedInAuthorizeViewFailed:self];
 }
 }
 else {
 [self dialogDidSucceed:url];
 }

 return NO;
 }

 if (raw && strstr(raw, "cancel=Deny")) {
 [self denied];
 return NO;
 }
 if (navigationType != UIWebViewNavigationTypeOther) _webView.alpha = 0.1;
 return YES;
}

/*Strip out the token and auth_verifier from the url*/


- (void)dialogDidSucceed:(NSURL*)url {
 NSString* q = url.query;
 NSLog(q);
 NSRange verifier_start = [q rangeOfString:@"oauth_verifier="];

 NSRange start = [q rangeOfString:@"auth_token="];
 if (start.location != NSNotFound) {
 NSRange end = [q rangeOfString:@"&"];
 NSUInteger offset = start.location+start.length;
 NSString* token = end.location == NSNotFound
 ? [q substringFromIndex:offset]
 : [q substringWithRange:NSMakeRange(offset, end.location-offset)];

 NSString* verifier = [q substringFromIndex:verifier_start.location+verifier_start.length];
 if (token&&verifier) {
 if ([_delegate respondsToSelector:@selector(requestTokenReceived:withVerifier:)]){
 [_delegate requestTokenReceived:token withVerifier:verifier];
 }
 }
 else
 {
 if ([_delegate respondsToSelector:@selector(OAuthLinkedInAuthorizeViewFailed:)]){
 [_delegate OAuthLinkedInAuthorizeViewFailed:self];
 }        }
 [self removeFromSuperview];
 }
}

You’re done.  You’ve now got your oauth_verifier and your access token all lined up to store away or make immediate calls to the api.

English word list for solving Jumble puzzle

I’ve been playing around with the iPhone SDK, and decided on a “Jumble Solver” as my first as a good learning project. It’d include a whole bunch of objective C and general programming concepts – Recursion, delegation, Interface builder, inheritence, sqlite.

My general approach was to build a decision tree with the available characters in the jumble, and compare the leaf nodes with an english dictionary. Finding a sqlite database of words … or rather a list of words was really hard. I stumbled upon a word list in text format found at the national puzzle solvers website. To dump that into a database I used the following simple ruby script. You’ll notice I limited the words to less than 7 characters. The search algorithm is n! complexity so limiting the jumble to 7 characters proved optimal performance. :

require ‘sqlite3.rb’
db=SQLite3::Database.new(“WordDatabase.sql”)
word_list=File.open(‘words.txt’).readlines

word_list.each do |word|
if word.length <8
db.execute(“INSERT INTO words (word) VALUES (?)”, word.chomp)
end
end