Android TableView with custom rows: scrolling with list longer than screen causes problems

You must Login before you can answer or comment on any questions.

I'm not sure if this is a bug or something I'm missing. I have a simple TableView with several custom rows. By custom rows I mean I create a TableViewRow object and add a few views to it, instead of just providing a dictionary containing title and some other TableViewRow properties. See the API docs for TableView, specifically the "Creating Tables" section, to see the difference in row creation methods.

Anyhow, I also add a rowID property to each custom row (and to its children) so that my table click event listener can reference the proper row. The idea is that the event listener simply changes the background color of the row to blue when the row is clicked.

This all works great in iOS, and in Android when the number of rows in the table can all fit on the screen. But in Android, when I make more rows than can fit on screen, when I scroll down, I notice several issues:

1) One of the rows ends up not having the default red background and is apparently missing the label view. Yet when I click on that "broken" row, the event listener reports the correct rowID value via Ti.API.info().

2) After I have scrolled down to reveal the "broken" row, if I then start clicking elsewhere on the table, it starts acting erratically and not setting the background of the correct row to blue, yet the event listener still reports the correct rowID property, and I'm pretty sure I'm accessing the desired rowView correctly. Sometimes it will either not turn the row blue at all, and other times it will turn the wrong row blue.

I imagine internally that the mapping of the the TableViewRows and their child views are getting corrupted, but only when there are more rows than can initially fit on screen, and only in Android.

Here is the simple test case code to reproduce the problem:

var tableData = [];
 
var win = Ti.UI.createWindow({ backgroundColor: 'white' });
 
var table = Ti.UI.createTableView();
 
for (var i = 0; i <= 20; i++){
   // each of the below UI widgets gets rowID property because different platforms end up firing
   // gestures on different elements; however we don't care which element, as long as we know the
   // row of the table that fired an event
   var row = Ti.UI.createTableViewRow({
      className: 'row',
      rowID: i,
      height: 40
   });
 
   var rowView = Ti.UI.createView({
      layout: 'horizontal',
      backgroundColor:'red',
      rowID: i,
      width: Ti.UI.FILL, height: '100%'
   });
 
   var rowLabel = Ti.UI.createLabel({
      text: 'Row ' + i,
      rowID: i
   })
 
   rowView.add(rowLabel);
   row.add(rowView);
   tableData.push(row);
}
 
table.setData(tableData);
 
table.addEventListener('click', function(e){
   Ti.API.info('click in table row: ' + e.source.rowID);
   // attempt to change clicked row to backgroundColor: blue
   // note that data[0] is the single TableViewSection that gets automatically added when table.setData()
   // was called; rows is the actual array of TableViewRow objects, which the rowID property should correctly index;
   // then children[0] is the single rowView child that was added to the TableViewRow 
   table.data[0].rows[e.source.rowID].children[0].backgroundColor = 'blue';
});
 
win.add(table);
win.open();
This is using Titanium SDK 2.1.3.

2 Answers

Accepted Answer

I think the problems you're having are because you have set the className on the row. When you do this, rows are re-used when you scroll... that's the point of className.

Try removing className for what you're doing, and see if that solves the behavior issues.

Once confirmed, I would put className back for the rows (say 'normalRow'), and replacing your selected row with one of a different className (say 'selectedRow') using TableView.updateRow().

— answered 7 months ago by Shannon Hicks
answer permalink
1 Comment
  • Thanks Shannon for your good help. While I still see the issue I describe in (1) in Android (where scrolling down I see a non-formatted "error" row, besides that it mostly works correctly when I do the following things that you suggested:

    • only use custom data on the TableViewRow, not on children views
    • set different className when you style a row differently
    • in the event, instead of trying to update the attributes of an existing row and its child views, instead create a whole new TableViewRow and replace the old one using TableView.updateRow().

    Here is a reworked chunk of code that "mostly" works. I say mostly, because in Android, I still see strange behavior where the clicked row doesn't update correctly until I click it twice or scroll it offscreen then come back, etc.

    var tableData = [];
     
    var win = Ti.UI.createWindow({ backgroundColor: 'white' });
     
    var table = Ti.UI.createTableView();
     
    for (var i = 0; i <= 20; i++){
       var row = Ti.UI.createTableViewRow({
          className: 'rowNormal',
          rowID: i,
          height: 40
       });
     
     
       var rowView = Ti.UI.createView({
          layout: 'horizontal',
          backgroundColor: 'red',
          width: Ti.UI.FILL, height: '100%'
       });
       var rowLabel = Ti.UI.createLabel({text: 'Row ' + i});
       rowView.add(rowLabel);
       row.add(rowView);
       tableData.push(row);
    }
     
    table.setData(tableData);
     
    table.addEventListener('click', function(e){
       Ti.API.info('click in table row: ' + e.rowData.rowID);
       var newRow = Ti.UI.createTableViewRow({
          className: 'rowSelect',
          rowID: e.rowData.rowID,
          height:40
       });
       var newView = Ti.UI.createView({
          layout: 'horizontal',
          backgroundColor: 'blue',
          width: Ti.UI.FILL, height: '100%'
       })
       var newLabel = Ti.UI.createLabel({text: 'Row ' + e.rowData.rowID});
       newView.add(newLabel);
       newRow.add(newView);
       table.updateRow(e.rowData.rowID,newRow);
    });
     
    win.add(table);
    win.open();

    — commented 7 months ago by Garrett Gleim

Don't set "rowID" on the child objects, only set it on the row. In your event, try e.rowData.rowID instead of e.source

— answered 7 months ago by Shannon Hicks
answer permalink
1 Comment
  • While certainly this is good advice to use rowID only on the row and use e.rowData.rowID, I can't use e.rowData on other events besides click (e.g. touchstart, etc); other events don't provide e.rowData, and my app ideally needs to do things in reaction to those other events as well.

    I used the click event just to illustrate what I feel is some problematic behavior.

    — commented 7 months ago by Garrett Gleim

Your Answer

Think you can help? Login to answer this question!