Paged list loaded from sqlite in Adobe AIR
During my recent work on the Adobe Evangelists Blogroll application I wanted to implement a lazy-loaded/paginated List component with data coming from a local SQLite database. The reason for this was, of course, a memory usage consideration. Adobe Evangelists Blogroll is a mobile application so I didn’t want to load all available Post objects for each selected blog at once. I wanted it to be loaded dynamically as the user scrolls through the list. It turned out this wasn’t really difficult but there were few gotchas that I wanted to share:
- You have to implement your own
IListcomponent that throwsItemPendingErrorwhenever the item requested withgetItemAtfunction is not available yet. Unfortunately the Flex SDK doesn’t come with one built-in, but the good news is that you can usePagedArrayListclass I created; its source code is available here
- Implement a
createPendingItemFunctionfor theAsyncListViewcomponent. This function gets called whenItemPendingErroris thrown and in our case serves two purposes. One is to trigger fetching the next page of rows from the database. It also returns aStringwith text that will be displayed temporarily in missing rows of theList. When that data gets fetched from the database those missing rows will be replaced with the loaded ones. - One gotcha for the previous point is that if you are using
SQLConnectionin synchronous mode you will have to start fetching rows in the next frame after the call to thecreatePendingItemFunctionfunction. That is why in my example I’m usingcallLaterto execute thefetchRowsfunction. This problem doesn’t arise with an asynchronous database connection. - The last gotcha is most likely a bug in the Flex SDK. It occurs when the
AsyncListView.listproperty is set before its list object is initialized and its length value is set. In that caseinvalidIndexerror is thrown inside of LinearLayoutVector. The workaround I came up with is that right after setting the length property,PagedArrayListdispatchesFlexEvent.INITIALIZEevent; the application logic should handle it and programmatically set theAsyncListView.listproperty to thePagedArrayListinstance. For reference I filed a bug in Flex SDK Jira.
Below you can find the source code that resolves the gotchas above. Also the whole Flash Builder project with paged list implementation is available here.
<?xml version="1.0" encoding="utf-8"?> <s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:helpers="com.riaspace.helpers.*" preinitialize="windowedApplication_preinitializeHandler(event)" creationComplete="windowedApplication_creationCompleteHandler(event)"> <fx:Script> <![CDATA[ import mx.collections.errors.ItemPendingError; import mx.events.FlexEvent; protected var conn:SQLConnection; protected const PAGE_SIZE:int = 50; protected function windowedApplication_preinitializeHandler(event:FlexEvent):void { // Creating inmemory database conn = new SQLConnection; conn.open(); // Creating data_tab table var createStmt:SQLStatement = new SQLStatement; createStmt.sqlConnection = conn; createStmt.text = "CREATE TABLE data_tab (value_col TEXT)"; createStmt.execute(); // Inserting 1000 records to the table var insertStmt:SQLStatement = new SQLStatement; insertStmt.sqlConnection = conn; insertStmt.text = "INSERT INTO data_tab VALUES (?)"; for (var i:int = 1; i <= 1000; i++) { insertStmt.clearParameters(); insertStmt.parameters[0] = "value " + i; insertStmt.execute(); } } protected function windowedApplication_creationCompleteHandler(event:FlexEvent):void { // STEP 1 - query rows count var stmt:SQLStatement = new SQLStatement; stmt.sqlConnection = conn; stmt.text = "SELECT count(*) as rowsCount FROM data_tab"; stmt.execute(); var result:SQLResult = stmt.getResult(); // STEP 2 - set PagedArrayList.length equal queried rows count pagedArrayList.length = result.data[0].rowsCount; // STEP 3 - fetch actual data starting from 0 offset (1st row) fetchRows(0); } protected function fetchRows(offset:int):void { // Fetch data rows with specified limit which is our requested page size // and offset passed as parameter var stmt:SQLStatement = new SQLStatement; stmt.sqlConnection = conn; stmt.text = "SELECT * FROM data_tab LIMIT :limit OFFSET :offset"; stmt.parameters[":limit"] = PAGE_SIZE; stmt.parameters[":offset"] = offset; stmt.execute(); var result:SQLResult = stmt.getResult(); if (result && result.data) { for(var i:int = 0; i < result.data.length; i++) { // Setting return row at offset + i position pagedArrayList.setItemAt(result.data[i], offset + i); } } } private function createPendingItemFunctionHandler(index:int, ipe:ItemPendingError):Object { // In case synchronous database mode is used fetchRows function should be called // after return from this function. With asynchronous mode fetchRows function can // be called directly. callLater(fetchRows, [index]); // Returning a message to display return "List items are being fetched from database..."; } protected function pagedArrayList_initializeHandler(event:FlexEvent):void { // NOTICE: this is a workaround for a Flex bug that causes invalidIndex error // in LinearLayoutVector. It all works well when asyncListView.list property // is set after pagedArrayList is initialized and its length property is set. asyncListView.list = pagedArrayList; } ]]> </fx:Script> <fx:Declarations> <helpers:PagedArrayList id="pagedArrayList" initialize="pagedArrayList_initializeHandler(event)" /> </fx:Declarations> <s:List width="100%" height="100%" labelField="value_col"> <s:AsyncListView id="asyncListView" createPendingItemFunction="createPendingItemFunctionHandler" /> </s:List> </s:WindowedApplication>


Hi,
i use your code posted above. Thanks
Tahir Alvi
29 Dec 10 at 11:11 am
[...] For Android On Tablets // Article which is a useful read as I continue my travels with Galaxy Tab SQL lazy-loaded/paginated List component //As stated on the [...]
Todays Learning Journey | marcusjpotter
30 Dec 10 at 10:42 pm