views:

102

answers:

2

I'm having a problem with a ListActivity. I've extended ArrayAdapter and Overridden getView to fill in the data for each row. I throw some data from my Adapter's ArrayList into a TextView.

My problem is, when I scroll, the TextView of each row populates with text that is not in its corresponding data in the ArrayList. What I mean is: say I have a row at the top of my list with a TextView that is populated with emptystring. If I'm at the bottom of my list and see a row with a TextView populated with "bob" and I flick to scroll to the top, the row at the top may now have "bob" in its TextView, but the data at that index of my ArrayList does not contain "bob." It contains emptystring (actually null). If I continue scrolling up and down, other rows will populate (or erase) with data that doesn't correspond to what's in the Adapter's ArrayList.

To cause this to happen, I don't need to scroll the ListView fast. However, it appears the faster I scroll, the more rows get messed up.

Here is my code. I realize I'm using findViewById every time getView gets called, but that's beside the point. I'm calling it against convertView; so it should be grabbing the correct TextView on each row, yes?

@Override
public View getView(int position, View convertView, ViewGroup parent) {

    // get the View for this list item
    View v = convertView;
    if (v == null) {
        LayoutInflater vi = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        v = vi.inflate(R.layout.layout_mylistlist_item, null);
    }

    // get the next object
    MyListItem myItem = m_items.get(position);

    // set up the list item
    if (myItem != null) {
        TextView txtName = (TextView) v.findViewById(R.id.mylist_name);

        // set text
        if (txtName != null && myItem.getName() != null) {
            txtName.setText(myItem.getName());
        }
    }

   // return the created view
    return v;
}
+1  A: 

Exchange this

if (v == null) {
        LayoutInflater vi = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        v = vi.inflate(R.layout.layout_mylistlist_item, null);
    }

with this

if (v == null) {
        LayoutInflater vi = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        v = vi.inflate(R.layout.layout_mylistlist_item, parent, false);
    }

You have to pass "false" to the inflate(...) method.

If you have time, take a look at this year's I/O session about Android ListView: http://code.google.com/events/io/2010/sessions/world-of-listview-android.html

It's really worth watching it or at least scroll over the slides. They're also online.

Based on your feedback let me propose a way for binding the data with the ArrayAdapter. Maybe that solves your issue.

public class MyTestArrayAdapter extends ArrayAdapter<MyDataModel>{
   private final int resourceId;

   public MyTestArrayAdapter(Context context, int resourceId, List<MyDataModel> myDataModelList) {
      super(context, resourceId, myDataModelList);
      this.resourceId = resourceId;
   }

   @Override
   public View getView(int position, View convertView, ViewGroup parent) {
      if(convertView == null){
         LayoutInflater inflater = (LayoutInflater)getContext().getSystemService....
         convertView = inflater.inflate(resourceId, parent, false);
      }

      MyDataModel modelObj = getItem(position);

      TextView someDataView = convertView.findViewById(....);
      someDataView.setText(modelObj.getDataText());
      ...

      return convertView;
   }

}

I think you don't need to pass your data as a member of your ArrayAdapter. (I didn't try to compile nor run this, so it may need some adjustment)

Juri
The method inflate(int, ViewGroup) in the type LayoutInflater is not applicable for the arguments (int, boolean)
Andrew
I think you meant "v = vi.inflate(R.layout.layout_inbox_item, null, false);". I've tried this as well with the same result.
Andrew
sorry, fixed it. You need to pass "parent" also. Didn't see that before :)
Juri
Ya, I've tried that, too :P In the comment under my original post I spot the culprit. I just don't know why it is so.
Andrew
And you're sure that's the problem and not the missing "parent" you didn't pass in?? Here (http://stackoverflow.com/questions/3499768/custom-list-adapter-repeats-entries/3500461#3500461) someone had a similar problem and that solved it..
Juri
btw, what is `m_items`??
Juri
Yes, that is the problem. Your suggestion of using a different overloaded method is actually what I was originally doing. I changed it to just used 2 parameters to see if that would fix the problem before I created this post. If I remove that null check in the if-block the problem is fixed. m_items is an ArrayList of MyListItems and is a member of my Adapter. It holds the data for each item in the ListView.
Andrew
I'll edit the post and propose an alternativ solution...
Juri
Thanks for your replies. Your update is what I am essentially already doing. However, instead of "getItem(position);", I am getting from an ArrayList which is a member variable of my extended Adapter (m_items.get(position);). When I take out the null check in my if-block, I've essentially allowed the code to make my TextView become emptystring. I think not allowing this KEPT the TextView with that resource ID set to some other visible TextView's text. I would think that calling convertView.findViewById, would grab that specific one, but maybe Android handles memory different than I thought.
Andrew
hmm...could be. I would however strongly suggest to view the I/O session. The two guys talking there have built the ListView and talk about this stuff you're having problems with :)
Juri
+1  A: 

I think the problem is your if(myItem != null) check at the bottom.

Try adding an else statement to that block that sets the textview to an emptystring.

Like so:

// set up the list item 
TextView txtName = (TextView) v.findViewById(R.id.mylist_name); 
if (myItem != null) {  
    // set text 
    if (txtName != null && myItem.getName() != null) { 
        txtName.setText(myItem.getName()); 
    } else
    {
        txtName.setText("");
    }
} 
else
{
    txtName.setText("");
}

The convertview will still have the old data in it when it is passed to you. You need to intentionally overwrite it even if your data is invalid, or it will keep its old data.

CodeFusionMobile
Ya, I think you are correct. If I don't set txtName to SOMETHING, it will have old data from a different row in it.
Andrew
The real fun begins when you flick through a list really fast and the recycler can't keep up:)
CodeFusionMobile