Fetching Paginated Data in Titanium SDK: A Practical Guide to HTTPClient, Caching, and Infinite Scroll
Learn how to fetch paginated data in Titanium SDK using HTTPClient, cache responses, and implement infinite scroll. A step‑by‑step guide with code, trade‑offs, and best practices for mobile apps.
18 Oct 2025, 01:27 UTC

Problem: Pulling Huge Lists from a REST API
Many mobile apps need to display long lists—product catalogs, news feeds, social posts—fetched from a backend that returns data in pages. A naive approach of requesting the entire dataset on launch can choke memory, slow the UI, and waste bandwidth, especially on mobile networks. The challenge is to fetch pages on demand, keep the UI responsive, and avoid re‑downloading data that hasn’t changed.
Thesis: Use Titanium.Network.HTTPClient with a Cache Layer and Infinite‑Scroll Trigger
By combining Titanium.Network.HTTPClient for asynchronous requests, a simple cache (Titanium.Cache or local storage), and a scroll listener that loads the next page only when the user reaches the bottom, you can build a smooth, memory‑efficient list that feels native on iOS and Android.
1. Setting Up the ListView and Initial Load
Start with a ListView that has a single ListSection. The items array will hold the data objects returned by the API. The first page is fetched when the window opens.
var win = Ti.UI.createWindow({
title: 'Products',
layout: 'vertical'
});
var section = Ti.UI.createListSection();
var listView = Ti.UI.createListView({
sections: [section],
defaultItemTemplate: 'productTemplate'
});
win.add(listView);
// Load first page on open
win.addEventListener('open', function() {
loadPage(1);
});
win.open();
Template Definition (optional)
Define a template that matches your data keys. For example:
var productTemplate = {
childTemplates: [
{
type: 'Ti.UI.Label',
bindId: 'title',
properties: {
color: '#333',
font: {fontSize: 18, fontWeight: 'bold'},
left: 10, right: 10, top: 5, bottom: 5
}
}
]
};
Ti.UI.setDefaultItemTemplate('productTemplate', productTemplate);
2. The loadPage Function
This function builds a GET request, sets headers, parses JSON, updates the list, and stores the response in a cache. It also remembers the current page number and total pages (if provided by the API).
var currentPage = 0;
var totalPages = null;
function loadPage(page) {
var client = Ti.Network.createHTTPClient();
client.timeout = 10000; // 10 seconds
var url = 'https://api.example.com/items?page=' + page;
// Optional: set auth token
client.setRequestHeader('Authorization', 'Bearer ' + TOKEN);
client.setRequestHeader('Accept', 'application/json');
client.onload = function() {
try {
var response = JSON.parse(this.responseText);
var items = response.data; // adapt to your API structure
// Cache the raw JSON string
Ti.App.Properties.setString('page_' + page, this.responseText);
// Update pagination state
currentPage = page;
if (response.total_pages) {
totalPages = response.total_pages;
}
// Map API items to ListView format
var listItems = items.map(function(item) {
return {
title: {text: item.name},
properties: {itemId: item.id}
};
});
section.appendItems(listItems, {merge: true});
} catch (e) {
Ti.API.error('JSON parse error: ' + e.message);
showRetry();
}
};
client.onerror = function(e) {
Ti.API.error('Network error: ' + e.error);
showRetry();
};
client.send();
}
Retry UI
Keep a simple overlay that allows the user to tap to retry the failed request.
function showRetry() {
var overlay = Ti.UI.createView({
backgroundColor: 'rgba(0,0,0,0.6)',
layout: 'vertical',
width: Ti.UI.FILL,
height: Ti.UI.FILL
});
var label = Ti.UI.createLabel({
text: 'Tap to retry',
color: '#fff',
font: {fontSize: 20},
top: 50
});
overlay.add(label);
win.add(overlay);
overlay.addEventListener('click', function() {
win.remove(overlay);
loadPage(currentPage || 1);
});
}
3. Infinite Scroll Trigger
The ListView emits a scrollend event. When the user scrolls near the bottom, request the next page if it exists.
listView.addEventListener('scrollend', function(e) {
var visibleRows = e.visibleItems;
if (!visibleRows || visibleRows.length === 0) return;
var lastVisible = visibleRows[visibleRows.length - 1];
var threshold = 5; // Load next page when 5 items from the end
if (section.items.length - lastVisible.rowIndex <= threshold) {
if (totalPages && currentPage >= totalPages) return; // No more pages
loadPage(currentPage + 1);
}
});
4. Caching Strategy
Storing each page’s raw JSON in Ti.App.Properties is lightweight and persists across app launches. When a page is requested, check the cache first:
function loadPage(page) {
var cached = Ti.App.Properties.getString('page_' + page);
if (cached) {
// Simulate HTTPClient onload with cached data
this.responseText = cached;
this.onload();
return;
}
// … proceed with network request
}
For more advanced caching, consider Titanium.Cache or a local database (e.g., SQLite). Remember to implement an expiration policy or ETag validation to avoid stale data.
5. Clean‑Up to Prevent Memory Leaks
HTTPClient objects hold callbacks that reference the UI. When the window is closed, abort any in‑flight requests and nullify the reference.
win.addEventListener('close', function() {
if (client) {
client.abort();
client = null;
}
});
Trade‑Offs and Limitations
- Memory Footprint: Caching every page can grow quickly if the dataset is large. Use a size limit or LRU eviction.
- Stale Data: Cached pages may become outdated. Implement cache expiration or ETag/Last‑Modified headers to refresh when necessary.
- Server Load: Unthrottled infinite scroll can hammer the API. Debounce the scroll handler or enforce a minimum interval between page requests.
- Large Payloads: A single page containing thousands of items can still spike memory. Prefer server‑side streaming or reduce page size.
Next Steps
- Replace
api.example.comwith your real endpoint and adjust the JSON parsing logic. - Implement ETag handling: store the
ETagheader per page and sendIf-None-Matchon subsequent requests. - If you need SSL pinning, wrap the HTTPClient call with
Ti.Network.createHTTPClientand validate the server certificate inonload. - Test with real network conditions: throttle bandwidth, simulate disconnects, and verify the retry UI works.
- Profile memory usage on both iOS and Android to ensure the list stays within acceptable limits.
By following this pattern, you’ll deliver a responsive, data‑efficient list that scales with your backend and provides a smooth user experience across platforms.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.