Titanium SDK: When to Stay Cross‑Platform, When to Drop to Hyperloop or Native Modules
Decide the right level of platform‑specific code in a Titanium app. This guide covers requirements, the minimal design, trust boundaries, CI checks, and failure modes that might push you from the cross‑platform API to Hyperloop or a native module.
30 Jan 2026, 03:32 UTC

Problem & Takeaway
When building an Alloy app you often face a trade‑off: use the pure JavaScript Ti.* APIs that run on both iOS and Android, or drop into native code with Hyperloop or a packaged module. The choice affects testing, CI, security, and future maintenance. This note explains the minimal design that keeps the majority of the code cross‑platform, isolates the platform escape hatch behind a thin façade, and shows how to enforce the trust boundary and detect failure modes early.
Requirements
- Application must run on iOS ≥ 11 and Android ≥ 5.0.
- Feature set: HTTP networking, local persistence, and a single native API that is only available on iOS (e.g.,
AVAudioRecorder). - Build pipeline uses CI with Docker images that contain the exact SDK, Node.js, Xcode, and Android SDK/NDK versions.
- Secrets (API keys, OAuth tokens) must not be committed and should be injected at runtime via environment variables.
Smallest Suitable Design
The core of the app lives in Alloy’s MVC conventions. Controllers expose business logic; views are XML; models use the Ti.Database adapter only when data needs to be cached locally. The only cross‑platform API used for networking is Ti.Network.HTTPClient. The iOS‑only AVAudioRecorder is accessed through a Hyperloop façade module named audioRecorder.js. This keeps the rest of the code pure JavaScript and testable with the standard Alloy test runner.
Hyperloop Facade Example
// audioRecorder.js – runs on both platforms, but only iOS has real logic
const Hyperloop = require('hyperloop');
let recorder;
function init(options) {
if (!Ti.Platform.osname === 'iphone' && !Ti.Platform.osname === 'ipad') {
throw new Error('AVAudioRecorder is iOS only');
}
const AVAudioRecorder = Hyperloop.createClass('AVAudioRecorder');
recorder = new AVAudioRecorder();
recorder.prepareToRecord();
}
function start() {
if (!recorder) throw new Error('Recorder not initialized');
recorder.record();
}
function stop() {
if (!recorder) return;
recorder.stop();
recorder = null;
}
module.exports = { init, start, stop };
Notice the guard that throws if the platform is not iOS. The rest of the app imports this module and calls its API without knowing the underlying native call.
tiapp.xml Snippet
<ti:app xmlns:ti="http://ti.appcelerator.org" >
<property name="ti.sdk.version" value="7.5.0" />
<property name="node.version" value="14.17.0" />
<property name="hyperloop.enabled" value="true" />
<property name="modules><module>com.example.audio" version="1.0.0" /></modules></property>
</ti:app>
The ti.sdk.version pin ensures that every build uses the same Titanium runtime. The hyperloop.enabled flag turns on code generation.
Trust / Data Boundaries
All code – JavaScript, Hyperloop bindings, and any native modules – runs in the same process with the same permissions. There is no sandbox between them. Consequently:
- Tokens or secrets injected into
tiapp.xmlor the CI env are visible to the Hyperloop layer. Keep them in environment variables and read them at runtime only. - Native objects returned by Hyperloop (e.g.,
AVAudioRecorder) are JavaScript objects; any reference can be passed to any module. - Treat the entire bundle as one trust domain; any compromise of the JavaScript layer compromises the native layer.
Operational Checks
- Pin SDK and Toolchain
- tiapp.xml:
ti.sdk.version,node.version. - CI Dockerfile:
ARG NODE_VERSION=14.17.0,ARG TIBI_VERSION=7.5.0.
- tiapp.xml:
- Clean Build Verification
Runtitanium cleanbefore every build to avoid stale caches. - Hyperloop Codegen Validation
After every Titanium SDK bump, runtitanium hyperloop generateand assert that the generatedhyperloop-*.zipfile exists. - Native Module Compatibility
If a native module is used, verify the binary against the pinned SDK by runningtitanium module validate. - Runtime Test on Physical Devices
Simulators hide permission prompts; runtitanium build -p ios -T deviceandtitanium build -p android -T deviceon real hardware.
Failure Modes & Design Change Triggers
- Platform Divergence – A Ti.* API behaves differently on Android vs iOS. If a feature must work identically, move the logic into a Hyperloop or native module.
- Hyperloop Build Failure – After SDK upgrade,
titanium hyperloop generatefails due to removed or renamed native classes. This forces a redesign of the facade or a switch to a native module. - Native Exceptions Not Catchable – Some Objective‑C exceptions propagate to JavaScript as
TiError. If these crashes are frequent, encapsulate them in a native module that exposes a safe API. - Stale Build Caches – If a CI build produces a different binary than local builds, enforce
titanium cleanand verify the output hash. - Security Policy Change – If the app must store secrets in a secure enclave, the current trust model (single bundle) is insufficient; consider moving secrets to a native module that uses the platform keychain.
Concrete CI Example
# Dockerfile
FROM node:14.17.0
RUN npm install -g titanium
ENV TIBI_VERSION=7.5.0
RUN titanium sdk install $TIBI_VERSION
# .github/workflows/build.yml
name: Build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node & Titanium
uses: actions/setup-node@v3
with:
node-version: 14
- run: npm install -g titanium
- run: titanium sdk install 7.5.0
- run: titanium clean
- run: titanium build -p ios -T device -s -r
- run: titanium build -p android -T device -s -r
Conclusion
By keeping the majority of the code in Alloy and isolating the platform‑specific logic behind a single Hyperloop façade, you preserve testability, reduce the surface for bugs, and keep the CI pipeline simple. Pinning the SDK and toolchain, enforcing clean builds, and validating Hyperloop generation after every upgrade are the operational guardrails that make this architecture sustainable. If a feature grows beyond the thin façade or if the native API becomes a bottleneck, migrate to a packaged native module and record the change in the repository’s changelog. This disciplined approach ensures that the decision to cross‑platform or drop into native code is deliberate, reversible, and well‑documented.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.