Replace ScrollView Rows with Ti.UI.ListView and Alloy Collections in Titanium
Stop rendering hundreds of Titanium rows in a ScrollView. Use Alloy Collections with Ti.UI.ListView to recycle list items and reduce memory on mobile devices.
29 Oct 2025, 18:49 UTC

The scrolling problem on a single screen
Open a Titanium screen that lists hundreds of products, orders, or messages. If each row is a separate Ti.UI.View or TableViewRow inside a ScrollView, the runtime creates and keeps every label, image, and layout object in memory. On Android especially, scrolling becomes janky and the app can run out of memory before the user reaches the end of the list.
The practical fix is to stop materializing every row at once. Use an Alloy Collection to hold the data and Ti.UI.ListView to recycle a small set of item templates as the user scrolls. Only the visible rows exist as views; off-screen rows are plain JavaScript objects until they come into view.
Why Alloy Collection plus ListView fits large homogeneous lists
An Alloy Collection is a Backbone-style collection of models. It loads records from a local database or API response and lets you query, filter, and iterate without touching the UI. Ti.UI.ListView is a virtualized list control: it reuses item templates instead of allocating a new view for every record. This pairing works best when rows look similar—same fields, same layout—and differ mainly in text or image values.
It is not a universal replacement. Short lists, highly irregular rows, or screens with heavy per-row gestures can stay in TableView or ScrollView. The ListView wins when the list is long, uniform, and scroll performance matters.
Check the pinned SDK before building
Open tiapp.xml in the project root and confirm the sdk-version element matches the SDK you installed. Then run the Appcelerator CLI environment check from the project directory:
appc ti info
Where to run: terminal in the Titanium project root. Permissions: read access to project files; no elevated OS privileges. Expected result: the CLI prints the selected Titanium SDK and toolchain paths. Risk: if the pinned SDK is missing, the build can fail or silently use a different ListView API.
Worked example: bind a collection to a list view
Assume an Alloy model named item with attributes title and subtitle, and a collection named items. In the controller, fetch the collection, then map each model to a plain list-item object whose keys match the template's bindId attributes.
// app/controllers/list.js
var items = Alloy.Collections.items;
items.fetch({
success: function () {
var listItems = [];
items.each(function (model) {
listItems.push({
template: 'row',
title: { text: model.get('title') },
subtitle: { text: model.get('subtitle') },
properties: { itemId: model.get('id') }
});
});
$.list.sections[0].setItems(listItems);
},
error: function () {
// handle load failure; keep the previous section or show an empty state
}
});
$.list.addEventListener('itemclick', function (e) {
var model = items.at(e.itemIndex);
if (model) {
// open detail using model.get('id')
}
});
Template markup in the view
In the Alloy view, declare a ListView with a ListSection and a template whose child labels use bindId values matching the controller keys:
<Alloy>
<ListView id="list" defaultItemTemplate="row">
<Templates>
<Template name="row">
<Label bindId="title" />
<Label bindId="subtitle" />
</Template>
</Templates>
<ListSection />
</ListView>
</Alloy>
Handle clicks through the collection, not the view
Do not store the whole model inside a list item. Keep the item payload small—text, URLs, and an itemId. When the user taps a row, read e.itemIndex from the event and resolve it against the collection. Because itemIndex is relative to its ListSection, multiple sections or a search filter require an explicit index map instead of a global position.
Limitations and what to verify
- ListView templates are less flexible than arbitrary view hierarchies. Variable row heights, swipe actions, and custom per-row layouts are harder to implement.
- BindId names, defaultItemTemplate, and setItems behavior can differ between Titanium SDK majors. Pin the SDK in tiapp.xml and confirm the syntax for your pinned version.
- Recycled rows still need template-compatible heights. Very tall or variable-height rows can clip or stutter.
Verify on hardware, not only on an emulator. Emulator scrolling can look smooth while a physical device drops frames. Build the same screen to a physical Android device and an iOS device with your largest expected dataset. Use Android Studio Profiler or Xcode Instruments to watch frame time and peak memory while scrolling. Log collection.length, the number of objects passed to setItems, and the model id chosen in itemclick to prove the index mapping is correct.
Actionable close
Pick the worst-scrolling screen in your app. Migrate only that screen from ScrollView or TableView to ListView backed by an Alloy Collection. Measure on your slowest target device before and after. Keep the older pattern for lists that are short, visually irregular, or rely on complex per-row gestures.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.