- Titanium-specific recommendations
- App architecture recommendations
The Appcelerator-approved standard for apps developed on the Titanium platform specifies a single-context, modular pattern, with well-structured code, and well-organized resources. By following these standards, developers will create apps that meet the preceding checklist of Stable, Rapid, Performant, and Readable.
Avoid the global scope
Putting objects into the global scope can cause various problems:
- Objects placed in the global scope will not be automatically garbage collected. You'll have to manually
nullglobal objects to mark them ready for collection.
- It's easy to inadvertently overwrite an object in the global scope, because that variable is accessible so widely within your program.
- The global scope of app.js is not accessible from other contexts or within CommonJS modules. So, you can't just dump variables there so you can access them throughout your app.
For these reasons, avoid defining variables in the global scope. Objects are placed in the global scope when:
- You declare a variable outside of a function or CommonJS module. Using a modular pattern will alleviate this problem.
- You omit the
varkeyword when declaring a variable (within or outside of a function). So always use
varwhen declaring variables.
Avoid local objects in global event listeners
The following code will cause a memory leak because the locally scoped variables are referenced in a global event listener. This is because the program will need to retain the locally scoped vars in order for the global event listener to use them. The global event listener will also persist until the app exits or the listener is explicitly removed.
Global event listeners include those associated with Ti.App, Ti.Geolocation, Ti.Gesture, and so forth. The same problem is possible with non-global event listeners, like those you associate with a UI element. If that UI element remains valid in memory, any event listeners – and the objects they refer to – must also be kept in memory.
The above example is an anti-pattern that will eventually consume the app's available memory. It's important to note that this is a common anti-pattern that developers employ in browser-based environments too, where it causes the same result, so it is not unique to Titanium.
If you need to have a custom event, consider a method / callback that you can invoke later on. For the global events like location, network change, etc. it's highly recommended to place them in app.js. The general rule of thumb is global events handle global objects.
Do not name custom events with spaces
Defer script loading
Or, if you're not using CommonJS but building out a namespace:
Don't extend Titanium prototypes
Many users attempt to add to the Ti namespace as a means to persist data across contexts, extend / override native methods, etc. This can sometimes work but is very unreliable for the following reasons:
- Sometimes you might be able to store things on the namespace but it's not changeable (i.e. an array stored on the namespace might not be able to be modified - mutable, etc.). Other-times your stored objects will be completely null.
- Since this isn't an approved way of storing anything, there's no guarantee it will work in future releases of Titanium.
As a rule do not add to, or extend via the prototype, any object or module in the Titanium namespace. If you want to extend a core part of the Titanium API you should build a native module to accomplish this. If you're just looking for an extendible JS namespace, create your own (i.e.
Coding strategies for multiplatform apps
Branching in code is useful when your code will be mostly the same across platforms, but vary here and there. Long blocks of if...then code are difficult to read and maintain. Also, excessive branching will slow your app's execution. If you must use this technique, try to group as much code as you can within a branch and defer loading as much as possible to mitigate the performance penalty of branching.
Using platform-specific JS files is likely to be most useful when your code is mostly different across platforms. This removes long if...then blocks from your main code. Separating platform-specific code reduces the chances of an error that comes from accidentally using the wrong platform's API or property. However, you'll have to remember to apply changes and fixes to each of the platform-specific files. So this approach could increase your work rather than reduce it.
See Supporting Multiple Platforms in a Single Codebase for more information and code examples.
You should not include sensitive data in non-JS files. Simply renaming files with a .js extension is not a suitable alternative. Such files might not be supported on device. And, the Titanium build process removes them from the final build.
Set local variables to avoid calling native methods
Each time you request the value of a device-related property, Titanium has to query the operating system for the value. For example, if you read from
Ti.Platform.displayCaps.platformHeight, Titanium must take a "trip across the bridge" to request the value from the operating system. Doing so takes a few cycles and if used too frequently could possibly slow your program. Something like the following would be more efficient:
App architecture recommendations
Modular components with CommonJS
Other architectures are valid and meet the needs of many developers. Which you choose is ultimately up to you and your experiences
Custom objects as components
On the downside, this pattern is less performant than CommonJS modules. The rapid nature of this pattern can lead the developer to general, high-level bad practices and developer 'laziness'. Inheritance is vague or even non-existent. And critically, memory management can be difficult as object references can remain after they're no longer needed.
The same could be accomplished without the self-calling function, if you prefer:
The code below is a fragment of this pattern: