Choosing Ti.UI.ListView over Ti.UI.TableView for Long Scrolling Lists in Titanium SDK
Learn why Ti.UI.ListView reduces memory pressure and improves scroll smoothness for large, uniform lists, and see a concrete infinite‑scroll inbox example.
07 Oct 2025, 23:07 UTC

Problem: TableView creates a view hierarchy for every row
When you build a feed, inbox, or chat log with Ti.UI.TableView, each row becomes a full tree of Titanium view objects (ImageView, Label, etc.). With thousands of rows this means thousands of live view instances, which consumes RAM and forces the UI thread to do extra layout work. On low‑end Android devices the result is noticeable scroll jank and higher memory usage.
Thesis: ListView reuses views through ItemTemplates and ListDataItem objects
Ti.UI.ListView, introduced in the Titanium 3.x era, treats a row as a plain JavaScript object (ListDataItem) that describes data for a reusable ItemTemplate. The framework recycles a limited set of native view objects, updating their properties via bindId mappings instead of destroying and recreating the view hierarchy for each row. This keeps memory usage roughly constant regardless of list length.
How ListView works: templates, binding, and recycling
ItemTemplate and bindId
An ItemTemplate defines the static layout of a row using child templates such as Ti.UI.Label or Ti.UI.ImageView. Each child is given a bindId string. When you supply a ListDataItem, you set properties that match those bindId keys; the ListView copies the values into the recycled views.
var template = {
childTemplates: [
{ type: 'Ti.UI.ImageView', bindId: 'avatar', properties: { width: 40, height: 40 } },
{ type: 'Ti.UI.Label', bindId: 'title', properties: { left: 50, right: 10, top: 2 } },
{ type: 'Ti.UI.Label', bindId: 'time', properties: { left: 50, right: 10, bottom: 2, color: '#999' } }
]
};
ListSection and data items
Rows are grouped in ListSection objects. You create a section from an array of ListDataItem instances, each carrying an optional itemId for stable identity across updates.
var items = [];
for (var i = 0; i < 5000; i++) {
items.push({
avatar: 'images/avatar' + (i % 5) + '.png',
title: 'Message ' + i,
time: '10:' + (i % 60),
itemId: i // stable identifier
});
}
var section = Ti.UI.ListSection.create({ items: items });
Worked example: infinite‑scroll inbox of 5,000 messages
The following snippet shows a minimal Classic Titanium app that loads the first 100 rows, then appends more as the user approaches the end using the ListView marker API.
// UI setup
var listView = Ti.UI.ListView.create({
templates: { 'default': template },
defaultItemTemplate: 'default',
sections: []
});
// Initial data
function loadPage(start, count) {
var page = [];
for (var i = start; i < start + count; i++) {
page.push({
avatar: 'images/avatar' + (i % 5) + '.png',
title: 'Message ' + i,
time: '10:' + (i % 60),
itemId: i
});
}
return page;
}
var pageSize = 100;
var offset = 0;
function appendPage() {
var newItems = loadPage(offset, pageSize);
var newSec = Ti.UI.ListSection.create({ items: newItems });
listView.appendSection(newSec);
offset += pageSize;
}
// Initial load
appendPage();
// Marker: show a loading indicator when near the end
listView.setMarker(pageSize - 20); // trigger 20 rows before the end
listView.addEventListener('marker', function(e) {
if (offset < 5000) appendPage();
});
// Click handling – stable identity via itemId
listView.addEventListener('itemclick', function(e) {
var item = e.section.getItemAt(e.itemIndex);
alert('Clicked ' + item.title + ' (id=' + item.itemId + ')');
});
win.add(listView);
Only a small set of native view objects (one per visible row plus a few off‑screen buffers) exists at any time, keeping memory flat and scroll smooth even on modest hardware.
Trade‑offs and limitations
- Homogeneity requirement: ListView expects rows to share the same structure. If you need wildly different layouts per row, you must define multiple
ItemTemplateobjects and switch via thetemplateproperty on eachListDataItem. This adds complexity compared to TableView’s ability to return any view hierarchy fromtableView.rows. - Limited freeform content: Arbitrary per‑row view nesting (e.g., embedding a custom view hierarchy built at runtime) is not possible; you must pre‑declare the template.
- Header/footer handling: Section headers and footers are defined via
headerTitle,footerTitle, or separateHeaderTemplate/FooterTemplateobjects, which differ from TableView’s simpleheaderViewandfooterViewproperties.
These constraints mean ListView is best suited for lists where the row layout is uniform or can be expressed with a small set of templates.
Actionable decision rule
Choose Ti.UI.TableView when:
- The list is short (under a few hundred rows) or highly heterogeneous.
- You need quick prototyping with arbitrary view hierarchies per row.
Choose Ti.UI.ListView when:
- The list is long or potentially unbounded.
- Rows share a common structure (or a limited set of templates).
- Scroll performance and memory usage are critical, especially on low‑end Android devices.
Regardless of choice, validate on a real device (or a device‑emulation profile that mimics low‑end specs) rather than relying solely on the simulator. Use the Titanium Profiler or Android Studio’s Memory tool to observe view count and GC activity while scrolling.
Practical verification steps
- Build two small projects: one using TableView, the other using ListView with the same data set (e.g., 5,000 rows).
- Run each on a low‑end Android device (e.g., a Snapdragon 450‑based phone with Android 12).
- Enable the Titanium Profiler (
ti info -p) and watch the View Count metric while scrolling rapidly through the list. - Confirm that the ListView version shows a relatively constant view count (≈ visible rows + buffer) whereas the TableView version’s count grows with the number of rows rendered.
- Observe frame‑time spikes; ListView should exhibit fewer >16 ms frames.
If you see the expected behavior, you have empirical evidence that ListView delivers the promised memory and scroll advantages for your use case.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.