Wednesday, March 21, 2012

Titanium Breaks

I should probably re-deploy and it'll work...
I've just finished implementing the ROT-avdraget app for iOS as well as for Android. The iOS app was made with Xcode, and the Android app was made with its native tools plus the excellent IntelliJ IDEA.

For the last few days I have tried to re-implement the app using Appcelerator Titanium. The question was whether I could save any time using a cross-platform tool instead of developing in the native environments. The result has been very disappointing. It's difficult even to get the provided sample apps to run, particularly on the Android emulator. Also you can't debug on hardware, so you have to rely on the buggy emulator support.

If only Titanium worked it could be a really nice environment. Most of the development time with the ROT-avdraget app was spent creating the user interface. I found both the iOS interface builder and the Android xml layouts to have a lot of room for improvement. With Titanium the gui is created in code and I think that is a good thing. I found it easier to put views in their right place with Titanium JavaScript code than with the native approaches.

If Titanium is ever going to be good it has to abstract away the differences between the native platforms (when that makes sense). Let me show you a specific example how that fails.

At the top of the screen there is a webview that shows a banner. Before the banner loads, an imageview displays a default image on top of the webview. As soon as the page load has completed, the default image fades away. If the page load fails we wait for 60 seconds and try again.

For iOS the code is very straightforward.
  • In webView:didFailLoadWithError we wait 60 seconds and then try again.
  • In webViewDidFinishLoad: the default imageview is faded out.
This is the code:

- (void)webView:(UIWebView *)webView
        didFailLoadWithError:(NSError *)error
{
    [self performSelector:@selector(openUrlInTopBanner)
          withObject:nil
          afterDelay:60];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    [self fadeOutImageToShowBeforeBannerLoads];
}

For Android the logic is slightly different.
  • onReceivedError() sets a failure flag, and then creates a timer for reload.
  • onPageFinished() will get called on both success and failure which is why we need to check the flag before fading out the default imageview.
This is the code:

public void onReceivedError(WebView view, int errorCode, String description, String failingUrl)
{
    isFailedTopBannerPageLoad = true;
    if (topBannerLoadTimer == null) topBannerLoadTimer = new Timer("topBannerLoadTimer");
    topBannerLoadTimer.schedule(new TimerTask()
    {
        public void run()
        {
            isFailedTopBannerPageLoad = false;
            openUrlInTopBanner();
        }
    }, 60 * 1000);
}

public void onPageFinished(WebView view, String url)
{
    if (!isFailedTopBannerPageLoad)
    {
        fadeOutImageToShowBeforeBannerLoads();
    }
}

And here's the Titanium version of the same bit of functionality. The code is a reflection of the iOS code above:
 
topBanner.addEventListener("error", function(e) {
    setTimeout(function() {
        openUrlInTopBanner()
    }, 60 * 1000);
});

topBanner.addEventListener("load", function(e) {
    fadeOutImageToShowBeforeBannerLoads();
});

It looks simple enough. When run on an iPhone it works exactly like the Objective-C code. But bad things happen as you run it on Android:
  • The load event listener gets called when the page succeeds. Good!
  • The load event listener gets called also when the page fails to load. What? Hmm, so it works like the native Android event listener?
  • The error event listener never gets called.
  • Titanium Fail!
So I would assume there are workarounds. The problem is that there had better exist a truck load of workarounds. I kept a little log during the development. Here is part of that log:
  • The New Project Wizard fails to find the Android sdk! Not until I create an empty folder for an old version of the sdk does it succeed to locate the sdk.
  • The Hello World sample project fails to deploy to the iPhone simulator. On the second attempt... it mysteriously works.
  • The Hello World sample projects fails to deploy to the Android simulator. The second attempt also fails. After restarting Eclipse... it mysteriously works.
  • Deploy to an iOS device uses iTunes synchronization. Since I don't normally sync my iPhone with iTunes on my developer machine it means all the installed apps are deleted when I deploy the Titanium-built app.
  • The call to Ti.UI.createImageView() gives a runtime error. Workaround: Rebuild and redeploy and it works.
  • createImageView(): Android requires a slash at the beginning of a path. iPhone does not.

The Android simulator (or is it emulator?) is a story of itself.
  • Starting the simulator creates five console logs. Where does one look for error messages?
  • I need to disconnect the physical Android device to run the app on the simulator, or else I get "More than one device and emulator".
  • Android simulator hangs when deploying an app. And again. And again.
  • Rebooting computer. Trying again. Android simulator hangs on "get app.js"...
  • Finally the app installs and starts. I press the back button. Next deploy fails. I uninstall the app from within Android. Now it seems to work again, for a while.
I'm giving up on Titanium. It is just broken.

It seems I'm not alone:




Post a Comment

Archive