Universal Apps in Appcelerator

Appcelerator is by far the best mobile rapid development platform out there, but they’re lacking a major feature – Universal app support.  Universal App support is the ability for an app to run in native resolutions on both the iPhone and iPad platforms.  Until now, you had to produce two versions of your app – an iPhone app and an “HD” app for your iPad.  This has its benefits – apps typically selling for $0.99 on an iPhone will often go for $2.99 or more for the “HD” version, but what if you want to write a Universal App?  With Appcelerator, you’re out of luck, until now.

With a simple hack, you can get your application to be a Universal App.  Coles notes: Change the “Targeted Device Family” to iPhone/iPad in your Xcode Active Target settings.

Using XCode to work with Appcelerator

This hack is predicated on my preferred way of using Appcelerator: (hint: it’s not using the Titanium IDE). The Titanium IDE and build process is a horrible way to work with Appcelerator. If that was my first introduction to Appcelerator, I would never continue with it. Creating a project is buggy, rebuilding it buggy. Sometimes it picks up your changes, sometimes it does a full build and makes you wait forever, sometimes you want it to do a full build and it doesn’t; the whole process is just a bad hack.  So when I use Appcelerator, I:

  • Create the project with Titanium.
  • Open the .Xcodeproj file that it created with Xcode.
  • Never open Titanium again

You can reload your app much quicker by simply using the Console view in Xcode.  Once you build and run it with XCode, you can simply hit the “Restart” button, and your code will be instantly reloaded, no recompilation, nothing. There are however, some caveats (see here).

Universal Apps

There are two possible reasons why you’d want to write a universal app with Appcelerator: to produce one, universal version of your app, or just to minimize the amount of code you have to write and maintain between your normal and HD versions of your app. As far as I can tell, Appcelerator encourages you to write either an iPhone or iPad app, even though it gives you two distinct targets in XCode (App and App-iPad).  But writing two distinct apps, as I have done in the past, is a waste – you end up reimplementing way too much of your business logic and duplicating it in two places.  It’s much better to follow the DRY (Don’t Repeat Yourself) principle.

Universal App architecture

MVC Directory Structure

MVC Directory Structure

Regardless of if you actually plan on delivering two distinct apps (normal and HD) or not, you’ll want to architect your application in such a way that you can maximize the sharing of code.  This means using Object-oriented Javascript and a true MVC (Model-View-Controller) paradigm in your code organization.

First we’ll start off with app organization. This seems simple, but it isn’t.

The first thing an Appcelerator app does when loading your app is to run “app.js”.  So our app.js looks something like this:

1
2
3
4
5
<code>var window = Ti.UI.createWindow(
{
url: "views/main.js"
});
window.open();</code>

What this does is open up a new window, with a new execution context in the views/main.js file.  This is great, because when we include further javascript files, they will all be loaded from the 2nd-level directory (i.e. views, controllers, include, etc.).  If we had stayed within the first execution context, we wouldn’t have been able to organize our files so nicely, since everything would have to be loaded from the base directory. You can read more on execution contexts here.

Object-oriented Architecture

In modern programming app design, we want to write and maintain as little code as possible, which means re-using code wherever possible.  In order to do this with Appcelerator, we need to use good inheritance and OO design principles.

First, we should create a base controller class. This uses a custom inheritance scheme by John Resig (code attached at the end of the article).

controllers/app_controller.js

1
2
3
4
5
6
Ti.include("../include/inheritance.js");
 
var AppController = Class.extend({
// application logic that is common to your application
  doLayout: function() {}
});

Then, extend these classes for iPad and iPhone specific logic:

controllers/iphone_controller.js

1
2
3
4
5
6
7
8
9
Ti.include("../controllers/app_controller.js");
var iPhoneController = AppController.extend({
    // application logic that is specific to iPhones
  doLayout: function()
  {
    // Layout code specific to iPads
    this._super.doLayout();
  }
});

controllers/ipad_controller.js

1
2
3
4
5
6
7
8
Ti.include("../controllers/app_controller.js");
var iPadController = AppController.extend({
// application logic that is specific to iPads
  doLayout: function() {
    // Layout code specific to iPads
    this._super.doLayout();
  }
});

Then, in your views/main.js file:

1
2
3
4
5
6
7
8
9
10
11
var ipad = Titanium.Platform.osname === "ipad";
if (ipad) {
  Ti.include("../controllers/ipad_controller.js");
  var controller = new iPadController(win);
}
else {
  Ti.include("../controllers/iphone_controller.js");
  controller = new iPhoneController(win);
}
controller.doLayout();  // Get your hardware-specific controller to lay out your UI
controller.doSomething();

Voila! Lots of code-sharing and a nicely organized Object-Oriented app!

You may have noticed we’re not separating the controller code from the view code…  I must admit that I haven’t much figured out how to separate the controller logic from the view logic, mostly since controller code will invariably affect the iPhone’s UI widgets and there isn’t a lot of separation in this paradigm (unlike Ruby on Rails or other MVC frameworks where there is a clear delineation between views and controllers).  I’m sure this will be the discussion of a future post when I wrap my head around better organization.


(Contains 1 attachments.)

WordPress Themes