Sharing UI Code Across Platforms with Titanium Alloy MVC
Learn how Titanium’s Alloy framework lets you write UI once in XML/JavaScript/TSS and still access native APIs, with a concrete example, build steps, and known limitations.
22 May 2026, 23:53 UTC

Problem: Duplicated UI Logic Hurts Maintenance
When building the same screen for iOS and Android with classic Titanium, you often end up writing two sets of UI code—one in ui/iphone, another in ui/android. Any change to layout, styling, or event handling must be duplicated, increasing the chance of drift and making refactors tedious.
Thesis: Alloy MVC Gives a Single Source of Truth for UI
Titanium’s Alloy framework adds a declarative MVC layer on top of the classic SDK. UI markup lives in XML, logic in JavaScript controllers, and appearance in TSS (Titanium Style Sheets). During the build, Alloy compiles these files into standard Titanium code, so you still get full access to native modules, Hyperloop, or any Ti.API you need.
Section 1: Project Layout Alloy Creates
After running the Alloy‑enabled project command, the app directory contains three subfolders that map directly to MVC concerns:
views/– XML files describing the visual hierarchy.controllers/– JavaScript files that receive lifecycle events ($.index,$.button) and bind data.styles/– TSS files that style selectors like"#myButton"or"Label".
Section 2: Worked Example – A Reusable List Item
Suppose you need a list of items that shows a title and a subtitle, and tapping a row opens a detail window. The following steps illustrate how Alloy lets you write this once.
- Create the project (run in a terminal with the Titanium CLI installed):
Permissions needed: ability to executeti create -p android -n AlloyListDemo --alloy cd AlloyListDemotiand write to the workspace directory. No special OS privileges are required. - Define the view (
app/views/index.xml):<Alloy> <Window class="container" > <ListView id="itemList" defaultItemTemplate="template" > <Templates> <ItemTemplate name="template" > <Label bindId="title" class="title" /> <Label bindId="subtitle" class="subtitle" /> </ItemTemplate> </Templates> <ListSection> <ListItem title="Item 1" subtitle="First subtitle" /> <ListItem title="Item 2" subtitle="Second subtitle" /> </ListSection> </ListView> </Window> </Alloy> - Add styles (
app/styles/index.tss):.container { backgroundColor: white } .title { font: { fontSize: 18, fontWeight: bold }; left: 10; top: 5 } .subtitle { font: { fontSize: 14 }; left: 10; top: 5 } - Optional controller logic (
app/controllers/index.js) to handle item clicks:$.itemList.addEventListener('itemclick', function(e) { var item = e.section.getItemAt(e.itemIndex); alert('Clicked: ' + item.title.text); }); - Build and run (Android example):
Watch the console for the lineti build -p android -l debug[INFO] Alloy compiling...before the standard Titanium compile step. This confirms the Alloy‑to‑Titanium translation occurred. - Verify the generated code: after the build finishes, look inside
build/android/assets/app/(orResources/for classic projects). You will see a compiledapp.jsthat contains the equivalent Titanium UI creation code—proof that Alloy’s XML/TSS/JavaScript was transformed.
Section 3: Data Binding and Model-View Sync
Alloy also provides Model and Collection classes that automatically update the UI when underlying data changes. For instance, replacing the static ListSection with a collection bound to a remote API lets the list refresh without manual appendSection calls.
var items = Alloy.createCollection('Item');
items.fetch();
$.itemList.sections = [items];
When items triggers a change event, the list updates instantly.
Trade-offs and Limitations
- Debugging indirection: stack traces point to generated Titanium code. You can enable source maps with
ti build -p android -l debug --source-mapor inspect the intermediate files inbuild//assets/app/to map back to your Alloy sources. - Build step overhead: Alloy adds a compilation phase, increasing clean build times by a few seconds on large projects.
- When to drop to classic: highly custom native views or performance‑critical animations sometimes require writing plain Titanium or using Hyperloop directly inside a controller.
Practical way to check that the trade-off is acceptable: time a clean build with and without the --alloy flag on a representative module, and verify that the generated app.js size remains comparable to a hand‑written classic build.
Actionable Closing
If you’re maintaining separate iOS/Android UI files and find yourself copying fixes back and forth, try scaffolding a new Alloy project for a single screen. Keep the existing classic code alongside it—Alloy and classic can coexist in the same app folder. After you confirm the build works and the UI behaves identically on both platforms, migrate additional screens incrementally. This approach gives you the maintainability benefits of MVC without sacrificing access to the full Titanium native API stack.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.