Building a Cross‑Platform Login Screen with Alloy MVC in Titanium
Learn how to create a platform‑specific login UI using Alloy XML, style it with TSS, perform a network login, store credentials, and launch an Android intent—all in a single, maintainable project.
15 May 2026, 13:34 UTC

Desired Outcome
Produce a single Alloy project that displays a native‑looking login screen on both iOS and Android, performs an asynchronous POST to a REST endpoint, stores the returned token in the app sandbox, and, on Android, opens the device camera via a native intent.
Prerequisites
- Node.js 18+ and
npminstalled. - Appcelerator Titanium SDK 10.x (or newer) and Alloy CLI.
- Xcode 15+ for iOS builds and Android Studio 2024+ for Android builds.
- Basic knowledge of JavaScript and XML.
- Access to a test REST server that accepts JSON and returns a token.
Project Setup
- Create a new Alloy project:
titanium create -t appcelerator -n loginDemo - Navigate into the project folder:
cd loginDemo - Add the Android platform for intent testing:
titanium platform add android
Define the Platform‑Specific View
Alloy’s <View> component maps to the native UI widget for the current platform. Use <If> blocks to tweak properties that differ between iOS and Android.
<Alloy>
<Window title="Login" layout="vertical" backgroundColor="#fff">
<View height="50dp" width="300dp" backgroundColor="#e0e0e0">
<TextField id="username" hintText="Username" color="#000"/>
</View>
<View height="50dp" width="300dp" backgroundColor="#e0e0e0">
<TextField id="password" hintText="Password" passwordMask="true" color="#000"/>
</View>
<Button id="loginBtn" title="Login"/>
<If platform="android">
<Button id="cameraBtn" title="Open Camera (Android)"/>
</If>
</Window>
</Alloy>
Explanation of terms:
<If platform="android">renders its children only on Android.TextFieldis the native text input;passwordMaskhides input characters.- The
loginBtnwill trigger the network request.
Centralized Styling with TSS
Alloy’s TSS (Ti Style Sheets) lets you write CSS‑like rules that compile to native properties. Create app/styles/alloy.tss:
$.username, $.password {
left: 20dp;
right: 20dp;
top: 10dp;
}
$.loginBtn {
top: 20dp;
width: 200dp;
backgroundColor: #3f51b5;
color: #fff;
}
$.cameraBtn {
top: 10dp;
width: 200dp;
backgroundColor: #f44336;
color: #fff;
}
Controller Logic
The login.js controller wires UI events, performs the network call, and handles file storage.
function login() {
var client = Ti.Network.createHTTPClient({
timeout: 5000,
onload: function(e) {
try {
var response = JSON.parse(this.responseText);
if (response.token) {
Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, 'token.txt').write(response.token);
alert('Login successful! Token stored.');
} else {
alert('Login failed: no token received.');
}
} catch (ex) {
alert('JSON parse error: ' + ex.message);
}
},
onerror: function(e) {
alert('Network error: ' + e.error);
}
});
client.open('POST', 'https://example.com/api/login');
var payload = {
username: $.username.value,
password: $.password.value
};
client.send(JSON.stringify(payload));
}
$.loginBtn.addEventListener('click', login);
// Android‑only: open camera intent
if (OS_ANDROID) {
$.cameraBtn.addEventListener('click', function() {
var intent = Ti.Android.createIntent({
action: Ti.Android.ACTION_IMAGE_CAPTURE
});
Ti.Android.currentActivity.startActivity(intent);
});
}
$.index.open();
Key points:
Ti.Network.createHTTPClienthandles asynchronous GET/POST. Theonloadcallback receivesthis.responseText.- JSON parsing is wrapped in
try/catchto avoid crashes on malformed responses. - Token is written to
applicationDataDirectory, which is sandboxed and persists across app launches. - The Android intent uses
Ti.Android.createIntentwithACTION_IMAGE_CAPTUREto launch the device camera.
Expected Checks
- Build and run on an iOS simulator: verify the login screen shows, the button triggers a network call, and the token file appears in
Documents. - Build and run on an Android emulator: confirm the camera button appears and opens the camera app.
- Check console logs for
Ti.Networkevents; use Xcode Debugger or Android Studio Logcat to trace native API calls. - Open the sandbox directory via
ti run -p ios -T simulator -dand inspecttoken.txt.
Recovery & Rollback Options
- If the login button does not fire the network call, add
Ti.API.debug('login clicked');beforelogin()to confirm event binding. - When the Android intent fails (e.g., no camera), check for the
android.permission.CAMERApermission intiapp.xmland add it if missing. - To revert UI changes, comment out the
<If platform="android">block and rebuild. - Use
ti cleanto purge build caches if unexpected native files appear.
Limitations & Practical Tips
- Alloy XML does not expose every native property; for advanced features, switch to
Ti.UIwidgets in the controller. - Large JSON payloads can stall low‑memory devices; consider using
Ti.Network.createHTTPClientstreaming or caching the response. - Cross‑origin requests may be blocked by server CORS settings; ensure the API allows requests from your app’s domain.
- Always test on both platforms; platform differences can surface only at runtime.
Conclusion
By combining Alloy’s declarative XML, centralized TSS styling, and Titanium’s native APIs, you can build a clean, maintainable cross‑platform login flow that respects platform nuances. The example demonstrates how to keep UI logic in one place while gracefully handling platform‑specific behavior like Android intents and file storage.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.