4

I was using SimpleCursorAdapter with an xml file with some views defined in it:

<LinearLayout ...>
    <ImageView android:id="@+id/listIcon" />
    <TextView android:id="@+id/listText" />
</LinearLayout>

My aim was to set the text color of the TextView, and the background color of the LinearLayout (that is, each row in the ListView) programmatically; the color is returned from a database.

I was getting NPEs when trying to manipulate the TextView for example, after it had found it with no complaints:

TextView tv = (TextView) findViewById(R.id.listText);
tv.setTextColor(color); // NPE on this line

Which is fair; if there's multiple entries in the list, it's reasonable to assume that "R.id.listText" will not work. So I extended SimpleCursor Adapter:

public View getView(int position, View convertView, ViewGroup parent) {
    View row = super.getView(position, convertView, parent);
    TextView text = (TextView) row.findViewById(R.id.listText);
    // ImageView icon = (ImageView) row.findViewById(R.id.listIcon);

    // If there's an icon defined
    if (mIcon_id != 0) {
        // icon.setImageResource(mIcon_id);
    }

    // If text color defined
    if (mTextColor != 0) {
        text.setTextColor(mTextColor);
    }

    // If background color set
    if (mBackgroundColor != 0) {
        row.setBackgroundColor(mBackgroundColor);
    }
    return(row);
}

And I get two different errors:

  • A similar NPE is thrown at "text.setTextColor(mTextColor)"
  • If the lines with the ImageView are uncommented, I get a "ClassCastException: android.widget.TextView" where I am calling "row.findViewById(R.id.listIcon)"

For reference, I was trying to use Commonsware's sample code, applying it to my situation. link (pdf)


Changed to this:

public View getView(int position, View convertView, ViewGroup parent) {
    convertView = super.getView(position, convertView, parent);

    if (convertView == null) convertView = View.inflate(mContext, R.layout.theme_item, null);
    TextView text = (TextView) convertView.findViewById(R.id.listText_tv);
    ImageView icon = (ImageView) convertView.findViewById(R.id.listIcon_iv);

    // If there's an icon defined
    if (mIcon_id != 0) {
        icon.setImageResource(mIcon_id);
    }

    // If text color defined
    if (mTextColor != 0) {
        text.setTextColor(mTextColor);
    }

    // If background color set
    if (mBackgroundColor != 0) {
        convertView.setBackgroundColor(mBackgroundColor);
    }
    bindView(convertView, mContext, mCursor);
    return(convertView);
}

Now I get a ClassCastException in the next activity (on list item click). Nothing has been modified in the next activity; it worked when using a SimpleListAdapter for the list which had entries (upon which clicking would lead to Activity2), so I think it's still something I'm doing wrong in the this extended class.

ataulm
  • 15,195
  • 7
  • 50
  • 92
  • The ClassCastException aside, does this code work if your cursor only returns a single entry? – Quintin Robinson Mar 14 '11 at 20:25
  • @Quintin I was unable to check this, I reverted back to an old version, and the new one I tried with the suggestions below which worked. Do you know why a ClassCastException could have been thrown in this case? Upon clicking an entry in the now working (colors changed) list, it should open a new activity - I'm getting a ClassCastException in this one now (regular activity, no lists) where I didn't before. – ataulm Mar 14 '11 at 22:45
  • *Haven't tried _Project > Clean_ so I'll have a go at that when I get into work; searching on SO shows that the generated _R_ file can get out of sync to cause this issue. – ataulm Mar 15 '11 at 06:32
  • _Project > Clean_ solved the ClassClassExceptions :) – ataulm Mar 15 '11 at 08:01

4 Answers4

5

It's not true that convertView will always be an existing instance; you should check if it's null and then instantiate it. If not, you can change it just as you did.

This should be like:

public View getView(int position, View convertView, ViewGroup parent) {
    if(convertView == null)
        convertView = //inflate your row here
    View row = convertView;
    //Manipulate the row here
    return(row);
}
ataulm
  • 15,195
  • 7
  • 50
  • 92
Marcos Vasconcelos
  • 18,136
  • 30
  • 106
  • 167
  • Thanks man - I assume this solution is equivalent to how the Adapter would normally reuse views which are not visible? (i.e. have been scrolled out of user sight) – ataulm Mar 15 '11 at 08:02
  • 1
    Exactly, this is the way they reuse views to not inflate every time and save process time and memory. – Marcos Vasconcelos Mar 15 '11 at 13:56
1

I would modify the getView method:

public View getView(int position, View convertView, ViewGroup parent) {
    convertView = View.inflate(getContext(), R.layout.myLayout, null);
    TextView text = (TextView) convertView.findViewById(R.id.listText);
    ImageView icon = (ImageView) convertView.findViewById(R.id.listIcon);

    // If there's an icon defined
    if (mIcon_id != 0) {
      icon.setImageResource(mIcon_id);
    }

    // If text color defined
    if (mTextColor != 0) {
      text.setTextColor(mTextColor);
    }

    // If background color set
    if (mBackgroundColor != 0) {
      convertView.setBackgroundColor(mBackgroundColor);
    }

    return convertView;
}
Eric Nordvik
  • 14,656
  • 8
  • 42
  • 50
0

I think that you're getting NPE because you're trying to create a textview and an imageview in a view where they aren't there.

When you want inflate a ListView with entries from a database, in your activity you define main.xml whith a ListView:

<LinearLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:id="@+id/listView1">
</ListView>

and in the onCreate method you set the view to this xml with setContentView(R.layout.main);. Then you create your cursor to your database and your custom adapter:

    MySimpleCursorAdapter adapter = new MySimpleCursorAdapter(this, R.layout.entry,
                names, new String[] {Phones.NAME, Phones.NUMBER}, new int[] {
                R.id.listIcon, R.id.listText});
    startManagingCursor(cursor);
    ListView listView = (ListView) findViewById(R.id.listView1);
    listView.setAdapter(adapter);

and you define an entry.xml with your listIcon and listText, where the adapter points. In my example, I'm querying the names and numbers from contact list.

In your custom adapter, you should access to your textview and imageview inside getView or bindView without any problem.

Here you have and example to get all the contacts in your contact list with its picture, name and number, but using ListActivity instead of activity, and only one xml with two text views and an imageview. If you use ListActivity you don't need to use a ListView and you don't need to set the content view in the activity.

I hope it helps!

Community
  • 1
  • 1
Racker
  • 1,316
  • 1
  • 11
  • 13
-2

Don't forget to put : layout_width and layout_heigth for each of your views .

Tsunaze
  • 3,204
  • 7
  • 44
  • 81