API Development

Improving Titanium Native App Performance – A Real World Example

Using Titanium SDK, it’s possible for anyone to build native applications for iOS and Windows mobile devices using a combination of JavaScript, XML and JSON. The result isn’t a web-based “Hybrid App”, but apps with a fully native user interface.

There are some situations where an app written in any JavaScript-to-native-user-interface solution like Titanium may require some additional optimisations to ensure it’s running as smoothly as a traditionally-developed native application.

In my freelance work, I develop native apps using Titanium and the Appcelerator platform — most of the time I’m developing new applications, but there are cases where I’ve had to take on an existing Titanium app, that’s either live in app stores or hasn’t been finished or published yet.

Taking on anyone’s work can be a challenge — different developers can follow different development patterns and styles. Even with something like the Appcelerator Alloy MVC framework, it’s possible to have two application codebases that look very different.

In the past couple of years, I’ve had a couple of projects that I took on that were in real trouble — they were existing Titanium apps and were live in the app stores, and had significant, and potentially fatal performance issues.

In one case, the app was causing real problems. Described as being “laggy”, the client was expecting to have to redevelop it natively in both Swift and Java using two different developers and throwing away all the work that had been done so far. Even worse was the fact that the app had an average 1 star rating in the App Store and Play Store.

The app itself had different branded versions, each being listed separately in the App Store. Alloy had been used for development, but the code had been split per brand and per platform, so I was having to work with three branded apps across two platforms – that’s six different codebases split across either complete git repositories or branches!

Looking through the codebase, I was able to spot some key issues:

  • there were multiple codebases across platforms and brand
  • there was no Alloy “theming” in place to make it easier to rebrand
  • it was trying to look identical on iOS and Android
  • it used “hand made”, non-native UI components

The first task was to look at merging the codebases. This was achieved by checking out a copy of each repository or branch and comparing them. While comparing the code, I could see the differences between iOS and Android versions were minimal; typically differences with images, fonts and some basic TSS / XML styling.

Since the app was built with Alloy, the first task was to introduce theming. Alloy allows you to create a theme for each app design / brand, and have its assets, images, and even styles in separate theme folders per app. A simple change in the config.json file allows you to build a new version of the app in that theme.

Once Alloy themes were implemented, I had one codebase that could now be switched between for each client app, but this was a semi-manual process — Alloy doesn’t currently allow you to theme the TiApp.xml file which was crucial as each app had a separate name and bundle ID in the App Stores.

To help with these situations in the past, I wrote TiTh, or “Titanium Theme”, a tool that allows you to switch themes via the terminal. TiTh takes care of updating the config.json for you, but more importantly it can look into the theme folder for a TiApp.xml file, and copy that over the default root file in the project.

So, typing the following in the terminal:

$ tith set app1 ios

would setup the app for iOS using the theme called “app1”, copying its TiApp.xml file into the root of the project and setting the theme in the config.json.

In addition, the following:

$ tith set app1 android

would do the same for Android.

Once the theming was done, it was time to look at iOS and Android differences and merge them — in this case, much of the code was the same, but there were some styling changes and some XML differences. Comparing the files, it was possible to deal with the differences by adding some TSS styling overrides, and moving some specific XML differences into platform specific folders.

In Alloy, you can create a folder called “android” or “ios” in subfolders like /controllers, /views etc, and the relevant platform will use the files in that subfolder. This means you could have completely separate index.xml files, which could both use the same index.js. They could of course have different XML and JS files, but this should only be done when there is such a huge difference between platforms that trying to manage them in one file would be confusing and detrimental.

As part of the theming and re-organising work, assets were optimised and put in the right place — so any PNG files that were huge were sized and optimised — most importantly, they were placed in the correct folders.

It’s easy by default to place images in the /assets/images folder and use them for iOS and Android — the problem with that is that any @2x or @3x images in that folder are bundled with Android, even if they are not used. Separating out assets correctly into the platform folders ensures that only the relevant assets are packaged in each platform build.

At this point, I had one codebase for the app, covering themes and platforms, and with some additional scripts, I could generate builds for the simulator, devices and production builds.

It’s important at this stage to know that when using TiTh, you’re relying on it to handle the theming and copying of the TiApp.xml file, so I tend to add that file to the .gitignore file in my repo to ensure it’s not version controlled, and make sure the readme file is clear on how to build the app.

Once a single codebase was established, any fixes would apply to all variations of the app, so now it was time to move to specific performance issues.

With major releases of Titanium SDK, iOS and Android each year, it’s important that apps are kept up-to-date to minimise the amount of work it takes to keep them running smoothly. With this in mind, I updated the app to use the latest SDKs (iOS, Android and Titanium) and rebuilt it on both platforms. Full end-to-end testing was carried out and any issues were addressed including any module updates and removal of old / deprecated code.

Worth noting that with some Titanium SDK updates, new features are added that can remove the need to use specific native modules. It’s always a good idea to review new updates, and check what new APIs have been added that could mean you can remove a native module from the app.

Next, I moved on to how the app was structured — the first thing I noticed was the use of a custom navigation element. Instead of a Ti.UI.TabGroup, code had been written to emulate one. Visually, it looked ok, but there was a lot of code doing a lot of work to emulate how a built-in Tab Group worked.

A Tab Group is a pretty sophisticated UI component. With it, you can create multiple tabbed views, all of which can spawn sub-windows that slide in on iOS and pop open on Android. The Tab Group itself handles its own state in terms of what’s open and where, and it requires very little code to get it up and running.

By creating a manual Tab Group, the previous developer was having to do all this themselves. The Windows were now views, the Tab Group was a view containing subviews which contained images and text — they were having to manage state when clicking on each tab, and toggling the state of others. They were handling opening a new “Window” and sliding this in with custom navigation. They were handling placement of back buttons and the events that closed the view, and managing all the open “Windows” in the navigation stack.

There were thousands of lines of code reproducing something that’s provided for free in iOS and Android. More importantly, the simulated Tab Group was slower; more laggy and the animation style and speed were noticeably different to the native version.

When building apps, I’ll always aim to use built-in native components wherever possible — I end up writing less code and I know I’ll end up with a smoother interface on all platforms. “Keeping it simple” is my overriding objective when approaching any app, along with never repeating code and keeping the amount of code in controllers to a minimum.

Updating the code to use a native Tab Group was straight-forward, and resulted in removing thousands of lines of JavaScript code and more importantly, provided a super-smooth interface on both platforms. Opening sub windows felt snappy and fast — it changed the entire feel of the app.

With a build sent to the client, they noticed the difference immediately and were shocked at what little work was done (less than a couple of days work) to merge codebases, implement themes and improve overall performance.

As a result of the initial work carried out, I got the go-head to continue with further enhancement work.

The next task was to look at other parts of the app and how it was rendering content. I was looking for ways that I could speed up rendering, improve performance, reduce lag and reduce waiting time. In one case, the app had a mapping component which had two states — “list view” and “map view”, and toggling between them rebuilt the map each time. This was wasteful – especially if the device location hadn’t updated and no data had changed. Code was added to cache and improved this by optimising the refreshing process. The result was an “instant” feel of switching, because the UI wasn’t blocked by calling the server and reloading the view.

In another part of the app, improvements were made in rendering HTML content (using some existing modules to convert these to attributed strings on iOS) and improving list performance by replacing Ti.UI.TableView with Ti.UI.ListView.

Any native modules that were found to be unnecessary and doing something that could be achieved in JS were removed, especially taking into account Titanium SDK changes that now made these modules redundant.

As part of the review of code, care was taken to ensure that over-the-bridge calls were kept to a minimum. Typically with a JavaScript-to-Native platform, a “Bridge” is involved. This is the aspect of the platform that takes care of mapping the JavaScript objects to their native counterparts.

Most of the time, over-the bridge calls aren’t that expensive, but it’s always good to optimise them anyway. So, for example, let’s look at the following code:

var button = Ti.UI.createButton({width:100, height:50);
button.title = “Click me”;
button.color = “#fff”;
button.backgroundColor = “#888”;
button.borderRadius = 5;

This is a simple example written in classic Titanium, but shows that it’s making five bridge calls to create the button — adding it to a parent view would be a sixth call.

A better solution would be to use a single bridge call:

var button = Ti.UI.createButton({width:100, height:50, title: "Click me!", color: "#fff", backgroundColor:"#888", borderRadius: 5);

Alloy makes this much easier. And, by defining a button in XML and it’s styling in TSS, it only uses one call to create the button in this case.

Where over-the-bridge calls get expensive is when you call them in loops. Consider a Table View with say 50 rows — each row has two labels and an image view. So, creating the Table View itself is a single bridge call, then creating the rows is another 50 bridge calls. After that we need to create 50 labels, and 50 image views — that’s another 100 calls — we’re up to 151 calls so far. Now, we have to add the image view and label to the row — that’s 2 more per row, another 100 calls — we’re up to 251. Finally we have to add the rows to an array and pass this to the Table View with one last call.

That’s 252 over-the-bridge calls.

In comparison to this, and 250+ calls over-the-bridge, a List View binds the data to a template with a single call, resulting in all the binding being handled on the native side. The result is much faster data-binding and scrolling performance.

In the end, it probably took a few days to make all these changes, but they made a massive difference to the overall app performance. The client was so happy that I continued working for them with long contracts continuing development of new features and additional apps. More importantly, the new apps went from a 1 to 4 star average rating in the App Store and Play Store as a result of the work that was done. The client was very happy!

Stay tuned for my next blog where I’ll summarize my tips for improving app performance.