Architecture Note: Using Titanium SDK’s Alloy MVC for a Reliable Mobile App
A concise guide to defining requirements, building a minimal Alloy structure, setting trust boundaries, adding operational checks, anticipating failure modes, and knowing when to revisit the design.
02 Sept 2026, 00:34 UTC

Requirements
Before writing any Alloy code, capture the UI/UX flow as a list of screens, user actions, and data interactions. For each screen note:
- Primary view elements (labels, buttons, lists).
- User‑triggered events that require data fetch or mutation.
- Any platform‑specific behavior (e.g., orientation‑dependent layouts).
Translate each screen into an Alloy controller, its associated view (.xml), and stylesheet (.tss). Keep the mapping one‑to‑one so that a change in UI requirements only touches those three files.
Minimal Suitable Design
Start with the smallest set of layers that satisfies the requirements:
- Presentation layer – Alloy controllers, views, and stylesheets. Controllers should only handle view‑event wiring and delegate work to services.
- Service layer – Plain JavaScript modules that encapsulate network calls, data parsing, and lightweight persistence (e.g.,
Ti.App.Propertiesor a simpleTi.Databasewrapper). Keep models thin: use plain objects or arrays; avoid deep inheritance hierarchies. - Data layer – The actual storage mechanism (filesystem, SQLite, remote API). Expose it only through the service layer.
Example service module (app/lib/api.js) that wraps Ti.Network.HTTPClient:
// app/lib/api.js
function request(url, method, payload, onSuccess, onError) {
var client = Ti.Network.createHTTPClient({
timeout: 10000
});
client.open(method || 'GET', url);
client.setRequestHeader('Content-Type', 'application/json');
client.onload = function () {
try {
var resp = JSON.parse(this.responseText);
onSuccess(null, resp);
} catch (e) {
onError(new Error('Invalid JSON'));
}
};
client.onerror = function () {
onError(new Error('Network error: ' + this.error));
};
if (payload) {
client.send(JSON.stringify(payload));
} else {
client.send();
}
}
module.exports = { request: request };
Controllers import this module and call api.request, passing callbacks that update the view or store results.
Trust/Data Boundaries
Establish a clear boundary at the network layer:
- All outgoing HTTP calls go through the service module (
api.js). - The service validates status codes, parses JSON, and transforms errors into a uniform shape (
{err: true, message: ...}). - Controllers never call
Ti.Network.HTTPClientdirectly; they only interact with the service. - If you use third‑party modules (e.g., a custom analytics library), wrap them in a similar facade and limit their access to
Ti.FilesystemorTi.App.Properties.
This centralizes error handling and makes it easier to audit what data leaves the device.
Operational Checks
Add lightweight startup health checks that run once when the app launches (app/alloy.js or the first controller’s init):
- Verify required Titanium plugins or modules are present (
Ti.Filesystem.getFile(Ti.ResourcesDirectory, 'someModule.js')). - Check device orientation or screen size if your layout depends on it (
Ti.Gesture.isLandscape()). - Log the results via
Ti.API.infofor early detection in development and production builds.
Example health‑check snippet:
// app/alloy.js
function healthCheck() {
var ok = true;
if (!Ti.Filesystem.getFile(Ti.ResourcesDirectory, 'lib/api.js').exists()) {
Ti.API.error('Missing API service module');
ok = false;
}
if (Ti.Platform.osname === 'iphone' && Ti.Gesture.isLandscape() && !Ti.UI.orientation) {
Ti.API.warn('Unexpected orientation state');
}
return ok;
}
if (!healthCheck()) {
// Optionally show a fallback UI or disable features
}
Failure Modes and Mitigations
- Missing Alloy sync adapters – If you rely on
Alloy.syncfor model persistence, ensure the adapter is defined (Alloy.sync = require('sync/adapters/sqlite')). Missing adapters cause silent failures; test by attempting a model save and checking for errors. - Exhausted memory from large TableView data sets – Binding a huge array directly to a
TableViewSectioncan trigger OOM crashes on low‑end devices. Mitigate with pagination or lazy loading: fetch a page of rows (e.g., 20 items), append them to the section, and trigger the next fetch on scroll‑near‑bottom. - Unhandled exceptions in Hyperloop calls – When using Hyperloop to invoke native APIs, wrap the call in
try/catchand log anyException. Provide a graceful degradation path (e.g., show a toast and continue).
Centralize logging: create a lib/logger.js that forwards to Ti.API in debug mode and to a remote endpoint in release, ensuring you never lose trace of failures.
Design‑Change Triggers
Revisit the architecture when any of the following occurs:
- UI/UX requirements add a new screen that shares substantial logic with existing controllers – consider extracting that logic into a service or a reusable Alloy widget.
- Performance profiling shows frame drops >16 ms during list scrolling – evaluate moving to
ListViewwith item templates or implementing virtualization. - A new third‑party module requires direct access to
Ti.FilesystemorTi.App.Properties– assess whether a facade can keep the trust boundary intact. - The target Titanium SDK version increments and introduces breaking changes to Alloy code generation – run
ti sdk listandti infoto confirm compatibility before upgrading.
Verification Steps
- Check SDK and Alloy versions: run
ti sdk listandti infoin the project root; ensure the SDK version matches the one declared intiapp.xmland that the Alloy version reported aligns with your project’s dependencies. - Build a debug simulator build:
ti build -p ios -T simulator -l debug(or replaceioswithandroidas needed). Inspect the generatedResources/alloy.jsto confirm that controllers and views are compiled as expected. - Launch the app, trigger a network request (e.g., pull‑to‑refresh), and verify that the service module logs validation errors as defined in the trust boundary.
- Introduce a deliberate error (e.g., modify the mock server to return malformed JSON) and confirm that the centralized error handler catches it and displays a fallback UI or toast.
Limitations and Practical Checks
Even with the above safeguards, keep these limits in mind:
- The service module adds an extra indirection; for ultra‑low‑latency needs (e.g., sensor polling) you may bypass it after a security review.
- Alloy’s compilation step can obscure stack traces; enable source maps (
ti build -p ios -T simulator --source-map) to aid debugging. - Large image assets still consume memory; use
Ti.UI.ImageViewwithimage: Ti.Filesystem.getFile(...).read()and consider down‑sampling on the device.
To verify that pagination works, observe the log output from your service when scrolling: each fetch should request the next page offset and append only the new rows to the TableViewSection. If you see duplicate or missing rows, adjust the offset calculation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.