Lapad Lopotopov
Lapad Lopotopov

Reputation: 23

Clickable ListViews with headers and MergeAdapter crash the app onListItemClick

I am having a problem within an activity that returns a ListView or a combination of several ListViews together merged with MergeAdapter from Commonsware. It is a search activity that based on the search query would return results from 1 or several different categories. When the categories are more than 1, I insert a header, which is another ListView with only one row - the name of the category.

Some code:

public class FirstAdapter extends ArrayAdapter<Address> {
public FirstAdapter (Context context, int resource,
        int textViewResourceId, List<Address> objects) {
    super(context, resource, textViewResourceId, objects);
}

public View getView (int position, View convertView, ViewGroup parent) {
    View view = super.getView(position, convertView, parent);
    Address location = this.getItem(position);

    TextView textView = (TextView) view.findViewById(R.id.SearchResultTextAddress);
    textView.setText(location.address);

    return view;
}

}

Then I have a header adapter:

public class HeaderAdapter extends ArrayAdapter<String> {
private String header;

public HeaderAdapter(Context context, int resource,
        int textViewResourceId, String header) {
    super(context, resource, textViewResourceId, new ArrayList<String>());

    this.header = header;
}

public void setVisible(boolean visible) {
    if (visible) show();
    else hide();
}

public void show() {
    clear();
    add(this.header);
}

public void hide() {
    clear();
}

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

public String getHeader() {
    return header;
}

public void setHeader(String header) {
    this.header = header;
}

@Override
public boolean isEnabled(int position) {
    return false;
}

}

Which as you can see will show only if necessary. And in my very activity I have a onListItemClick overridden:

@Override
protected void onListItemClick(ListView listview, View view, int position, long id) {
    super.onListItemClick(listview, view, position, id);

    if (this.firstAdapter.getCount() > 0) {
        position--;
        if (position < this.firstAdapter.getCount()) onRepClick(this.firstAdapter.getItem(position));
        position = position - this.firstAdapter.getCount();
    }

    if (this.secondAdapter.getCount() > 0) {
        position--;
        if (position < this.secondAdapter.getCount()) onAusschussClick(this.secondHeader.getItem(position));
        position = position - this.secondAdapter.getCount();
    }

So everything works ok when I have only one category from one Adapter. The moment when I have results from two of them, I get an ArrayIndexOutOfBoundsException. I assume that my logic with getting the positions is not correct. Any thoughts would be appreciated, thanks.

Here is the stack trace:

06-28 12:56:54.291: E/AndroidRuntime(3963): FATAL EXCEPTION: main
06-28 12:56:54.291: E/AndroidRuntime(3963): java.lang.ArrayIndexOutOfBoundsException
06-28 12:56:54.291: E/AndroidRuntime(3963):     at java.util.ArrayList.get(ArrayList.java:313)
06-28 12:56:54.291: E/AndroidRuntime(3963):     at android.widget.ArrayAdapter.getItem(ArrayAdapter.java:298)
06-28 12:56:54.291: E/AndroidRuntime(3963):     at com.jasamer.callarep.SearchActivity.onListItemClick(SearchActivity.java:136)
06-28 12:56:54.291: E/AndroidRuntime(3963):     at android.app.ListActivity$2.onItemClick(ListActivity.java:319)
06-28 12:56:54.291: E/AndroidRuntime(3963):     at android.widget.AdapterView.performItemClick(AdapterView.java:284)
06-28 12:56:54.291: E/AndroidRuntime(3963):     at android.widget.ListView.performItemClick(ListView.java:3513)
06-28 12:56:54.291: E/AndroidRuntime(3963):     at android.widget.AbsListView$PerformClick.run(AbsListView.java:1849)
06-28 12:56:54.291: E/AndroidRuntime(3963):     at android.os.Handler.handleCallback(Handler.java:587)
06-28 12:56:54.291: E/AndroidRuntime(3963):     at android.os.Handler.dispatchMessage(Handler.java:92)
06-28 12:56:54.291: E/AndroidRuntime(3963):     at android.os.Looper.loop(Looper.java:130)
06-28 12:56:54.291: E/AndroidRuntime(3963):     at android.app.ActivityThread.main(ActivityThread.java:3835)
06-28 12:56:54.291: E/AndroidRuntime(3963):     at java.lang.reflect.Method.invokeNative(Native Method)
06-28 12:56:54.291: E/AndroidRuntime(3963):     at java.lang.reflect.Method.invoke(Method.java:507)
06-28 12:56:54.291: E/AndroidRuntime(3963):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:847)
06-28 12:56:54.291: E/AndroidRuntime(3963):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:605)
06-28 12:56:54.291: E/AndroidRuntime(3963):     at dalvik.system.NativeStart.main(Native Method)

Upvotes: 1

Views: 933

Answers (2)

Lapad Lopotopov
Lapad Lopotopov

Reputation: 23

Well, solution came out to be different and much simpler. In the if statement I had to change: if (position < this.firstAdapter.getCount()) to the follwing: if ((position < this.firstAdapter.getCount() && position >= 0))

In this way I would ensure that no negative numbers would be possible and index would be always correct no matter how many headers I have.

Upvotes: 1

CommonsWare
CommonsWare

Reputation: 1006869

I think you need some else or return logic in onListItemClick().

Let's say that position is 1, firstAdapter.getCount() returns 383, and secondAdapter.getCount() returns 27. You will go through the first if block (383 is greater than 0), where you subtract one from position (which is now 0) and process it. You will then go through the second block (27 is greater than 0), where you subtract one from position (which is now -1), and you will crash trying to get at the -1 element. Since there is only one row identified by position, it can only be in one of the adapters, and your existing code does not handle that case.

Upvotes: 0

Related Questions