ClassCastException in adapter with 2 layouts

730 Views Asked by At

I have a adapter that has inflates two layouts(one for separator,one for normal items), but when I run the app I get this in logCat:

java.lang.ClassCastException: com.myapp.android.adapter.ModelItemAdapter$ViewHolderSeparator cannot be cast to com.myapp.android.adapter.ModelItemAdapter$ViewHolderItem
            at com.myapp.android.adapter.ModelItemAdapter.getView(ModelItemAdapter.java:84)
            at android.widget.AbsListView.obtainView(AbsListView.java:2019)
            at android.widget.ListView.makeAndAddView(ListView.java:1772)
            at android.widget.ListView.fillDown(ListView.java:672)
            at android.widget.ListView.fillGap(ListView.java:636)
            at android.widget.AbsListView.trackMotionScroll(AbsListView.java:4567)
            at android.widget.AbsListView.scrollIfNeeded(AbsListView.java:2866)
            at android.widget.AbsListView.onTouchEvent(AbsListView.java:3121)
            at android.view.View.dispatchTouchEvent(View.java:5564)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2052)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1813)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2058)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1827)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2058)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1827)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2058)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1827)
            at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2058)
            at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1827)
            at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1931)
            at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1390)
            at android.app.Activity.dispatchTouchEvent(Activity.java:2364)
            at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1879)
            at android.view.View.dispatchPointerEvent(View.java:5766)
            at android.view.ViewRootImpl.deliverPointerEvent(ViewRootImpl.java:2890)
            at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2466)
            at android.view.ViewRootImpl.processInputEvents(ViewRootImpl.java:845)
            at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2475)
            at android.os.Handler.dispatchMessage(Handler.java:99)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:4441)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:511)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
            at dalvik.system.NativeStart.main(Native Method)

And here is my code:

public class ModelItemAdapter extends ArrayAdapter<ListModelItem> {

    // == constants ==
    public static final String TAG = ModelItemAdapter.class.getSimpleName();

    // == attributes ==
    private final Context context;
    private final List<ListModelItem> modelsArrayList;
    private LayoutInflater inflater;

    // == constructor ==
    public ModelItemAdapter(Context context, ModelItemsList modelItemsList) {
        super(context, R.layout.list_item_row, modelItemsList.getItems());
        this.context = context;
        this.modelsArrayList = new ArrayList<ListModelItem>(modelItemsList.getItems());
        inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }


    // == public methods ==
    @Override
    public int getCount() {
        return modelsArrayList.size();
    }

    @Override
    public ListModelItem getItem(int position) {
        return modelsArrayList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public int getViewTypeCount() {
        return super.getViewTypeCount();
    }

    @Override
    public int getItemViewType(int position) {
        return super.getItemViewType(position);
    }

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

        Log.d(TAG, "pos= " + position + " parent= " + parent);
        Log.d(TAG, "item= " + getItem(position));


        ListModelItem itemAtPos = modelsArrayList.get(position);


        View rowView = convertView;
        ViewHolderItem holder;

        if (!itemAtPos.isParentOrMyObject() ) {


            if (rowView == null) {
                rowView=inflater.inflate(R.layout.list_item_row, parent, false);
                holder = new ViewHolderItem();

                holder.imgView = (ImageView) rowView.findViewById(R.id.item_icon);
                holder.titleView = (TextView) rowView.findViewById(R.id.item_title);
                holder.counterView = (TextView) rowView.findViewById(R.id.item_counter);
                rowView.setTag(holder);
            } else {
                holder = (ViewHolderItem) rowView.getTag();
            }


            Log.d(TAG, "modelType= " + itemAtPos.getModelType());

            Integer resIconId = itemAtPos.getIconResId();
            Log.d(TAG, "resIconId= " + resIconId);

            if (itemAtPos.hasIcon()) {
                holder.imgView.setImageResource(resIconId);
            }

            String itemTitle = context.getString(itemAtPos.getDisplayValueResId());
            holder.titleView.setText(itemTitle);
            holder.counterView.setText(itemAtPos.getTotalCountString());

        } else {
            ViewHolderSeparator separator;
            if (rowView == null) {
                rowView = inflater.inflate(R.layout.title_item_row, parent, false);
                separator = new ViewHolderSeparator();
                separator.title = (TextView) rowView.findViewById(R.id.header);
                rowView.setTag(separator);
            } else {
                separator = (ViewHolderSeparator) rowView.getTag();
            }

            // assume that it is company.
            String title = itemAtPos.getCompanyName();

            if (itemAtPos.isMyObject()) {
                title = context.getString(itemAtPos.getDisplayValueResId());
            }

            separator.title.setText(title);
        }

        return rowView;
    }

    // == view holder ==
    static class ViewHolderItem {
        ImageView imgView;
        TextView titleView;
        TextView counterView;
    }

    static class ViewHolderSeparator {
        TextView title;
    }
}
2

There are 2 best solutions below

0
Blackbelt On BEST ANSWER

Change the implementation of getViewTypeCount and getItemViewType like:

    @Override
    public int getViewTypeCount() {
        return 2;
    }

    @Override
    public int getItemViewType(int position) {
        if (getItem(position).isParentOrMyObject()) {
                return 0;
        }
        return 1;
    }

this way you will get two differents convertView. The one at index 0, when isParentOrMyObject() return true, the one at index 1 when it return false

1
JstnPwll On

The view holder which is attached to the recycled view (convertView) is not guaranteed to be the same type as the item at the current position. For example, the view + view holder might be a recycled Item, but the current index position is attempting to inflate or show a Separator.

You'll have to check whether the item type of the recycled view is different than the current item type and re-inflate/re-tag the view if it is different (the same way you do if convertView is null).

Alternatively, you could switch to one layout & one view holder class and selectively hide or show certain parts of the layout depending on which type it is.