0
votes

I'm using MergeAdapter because I need a listview of dynamic added/removed headers(sections) and their relevant custom adapters. When adding a new String or photo or video or a group of data content at one time, they will be added into this merged list and show relevant headers. But the problem is when adding content, MergeAdapter.getView will return a TextView not RelativeLayout which is from my custom adapter's resource, then views from this custom adapter get null value and throws NullPointerException. Custom adapter named "CustomContentListAdapter" which extends from BaseAdapter Code:

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

    Content content = getItem(position);

    ///*
    if(rowView == null)
    {
        mCacheView = new CustomListCachedViewHolder();//a view holder
        rowView = mLayoutInfator.inflate(mResourceId, root, false);
        mCacheView.mToggleButton = (Button) rowView.findViewById(R.id.toggle_button);
        mCacheView.mAudioIcon = (ImageView) rowView.findViewById(R.id.audio_icon);
        mCacheView.mTextInput = (TextView) rowView.findViewById(R.id.content);
        mCacheView.mThumbnail = (ImageView) rowView.findViewById(R.id.photo_thumbnail);
        mCacheView.mCreateTimeLabel = (TextView) rowView.findViewById(R.id.created_time);
        mCacheView.mExpandButton = (ImageView) rowView.findViewById(R.id.expand_button);
        rowView.setTag(mCacheView);
    }
    else
    {
        mCacheView = (CustomListCachedViewHolder) rowView.getTag();
    }

    if(View.GONE != mCacheView.mToggleButton.getVisibility() || 
       View.GONE != mCacheView.mAudioIcon.getVisibility())
    {
        mCacheView.mToggleButton.setVisibility(View.GONE);
        mCacheView.mAudioIcon.setVisibility(View.GONE);
    }

    mCacheView.mExpandButton.setOnLongClickListener(new OnLongClickListener() {

        @Override
        public boolean onLongClick(View v) {

            return false;
        }
    });


    mCacheView.mThumbnail.setImageBitmap(null);
    mCacheView.mThumbnail.setVisibility(View.GONE);

    mCacheView.mCreateTimeLabel.setText("");
    mCacheView.mNoteEntry.setText("");


    return rowView;
  }

And its resource xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:layout_margin="5dp" >
   <RelativeLayout 
    android:id="@+id/content_list_wrapper"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="5dp">
    <RelativeLayout 
        android:id="@+id/detail_content_wrapper"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="5dp">          
        <ImageView 
            android:id="@+id/audio_icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:contentDescription="@string/demo_img"
            android:background="@drawable/audio_thumb"
            android:layout_centerVertical="true"
            android:layout_alignParentLeft="true"
            android:visibility="gone"
            android:layout_margin="5dp"/>
        <Button 
            android:id="@+id/toggle_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:contentDescription="@string/demo_img"
            android:layout_centerVertical="true"
            android:text="@string/dummy_text"
            android:layout_toRightOf="@id/audio_icon"
            style="@style/cutomEditTextStyle"
            android:visibility="visible"
            android:layout_margin="5dp"/>
        <ImageView 
            android:id="@+id/photo_thumbnail"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:contentDescription="@string/demo_img"          
            android:layout_toRightOf="@id/toggle_button"
            android:visibility="gone"
            android:layout_margin="5dp"/>
        <TextView 
            android:id="@+id/note_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/photo_thumbnail"
            android:padding="5dp"/>

    </RelativeLayout>
    <LinearLayout 
        android:id="@+id/expand_delete_combo_layout"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:orientation="horizontal"
        android:padding="5dp">
        <ImageView 
        android:id="@+id/expand_right_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@string/demo_img"
        android:background="@drawable/ic_ab_forward_holo_light"
        android:layout_gravity="center_vertical"
        android:layout_marginRight="10dp"/>
        <Button 
        android:id="@+id/delete_button"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:contentDescription="@string/demo_img"
            android:background="@color/color_red"
            android:visibility="gone"
            android:layout_margin="5dp"/>
    </LinearLayout>
</RelativeLayout>
<TextView 
    android:id="@+id/created_time"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@id/content_list_wrapper"
    android:layout_marginLeft="5dp"
    android:layout_marginTop="5dp"
    android:padding="5dp"/>
</RelativeLayout>

I use fragment to handle all the data, please see the below:

    for(Content content : contents)
    {
        List<SubContent> subContents = mContentManager.getListCopy(content.getUUID());
        if(!subContents.isEmpty())
        {
            TextView label = (TextView) View.inflate(mContext, 
                     R.layout.titlebar_layout, null);
            label.setText(mContext.getResources().getString(
                R.string.content, content.getSectionNum() + " " + content.getTitle()));
            mMergeAdapter.addView(label);
            adapter = new CustomContentListAdapter(
                    mContext, R.layout.note_list_item_layout, subContents);
            mCacheAdapterMap.put(content.getUUID(), adapter); // store existing adapter
            mMergeAdapter.addAdapter(adapter);
            mMergetAdapter2.setActive(adapter, true);
        }
    }

    mMergedListView.setDivider(
        mContext.getResources().getDrawable(R.drawable.devider_line));
    mMergedListView.setDividerHeight(1);
    mMergedListView.setAdapter(mMergeAdapter);
    mMergedListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

When adding new content, I did like this:

  public void notifyNewContentAdded(SubContent subContent)
  {
     String id = subContent.getParentId();
                CustomContentListAdapter adapter = mCacheAdapterMap.get(id);
                List<Content> subContents = mContentManager.getCurrentListCopy(id);

                Content content = mContentManager.getInstance().getCurrentContent(id);
                String title = "";
                if(null == adapter)
                {
                    TextView label = (TextView) View.inflate(
                        mContext, R.layout.titlebar_layout, null);
                    if(content != null)
                    {
                        title = content.getSectionNum() + " " + content.getTitle();
                    }
                    label.setText(
                        mContext.getResources().getString(R.string.content, title));
                    mMergeAdapter.addView(stepLabel);
                    if(!subContents.contains(subContent))
                    {
                        subContents.add(subContent);
                    }
                    adapter = new CustomNoteListAdapter(
                        mContext, R.layout.list_item_layout, subContents);

                    mCacheAdapterMap.put(id, adapter);
                    mMergeAdapter.addAdapter(noteAdapter);
                    mMergetAdapter.setActive(noteAdapter, true);

                }
                else
                {
                    if(!adapter.contains(subContent))
                    {
                        adapter.addItem(subContent);
                    }
                    adapter.notifyAdapterChange();
                }

                mMergeAdapter.notifyDataSetChanged();
                mMergeAdapter.notifyDataSetInvalidated();
}

I don't know why convertView in MergeAdapter.getView() will be TextView when the position comes to my custom adapter. And if I changed if(rowView == null) to if(rowView == null || rowView instanceof TextView), the NullPointerException will be gone. But another blocker jumps out when scrolling down the merged listview. It throws ArrayIndexOutOfBoundException. Log info as below:

11-18 13:26:24.687: E/ApplicationErrorHandler(10971): Entered uncaughtException
11-18 13:26:24.687: E/ApplicationErrorHandler(10971): java.lang.ArrayIndexOutOfBoundsException: length=8; index=9
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.widget.AbsListView$RecycleBin.addScrapView(AbsListView.java:8269)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.widget.AbsListView.trackMotionScroll(AbsListView.java:6629)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.widget.AbsListView.scrollIfNeeded(AbsListView.java:3910)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.widget.AbsListView.onTouchMove(AbsListView.java:4762)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.widget.AbsListView.onTouchEvent(AbsListView.java:4590)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.View.dispatchTouchEvent(View.java:8135)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2416)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2140)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2422)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2155)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2422)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2155)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2422)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2155)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2422)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2155)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2422)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2155)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2422)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2155)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2422)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2155)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2422)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2155)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2422)
11-18 13:26:24.687: E/ApplicationErrorHandler(10971):   at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2155)

It's surely highly appreciated with any constructive advice.

1
I don't know why in MergeAdaper.getView(), convertView could be TextView, which is supposed to be a section header, not real data row, which is the most complicated one. The real row view should be inflated from our custom resource xml and refer to the parent ViewGroup, RelativeLayout, LinearLayout or others.user3830579
That's why I got NullPointerException since the convertView is TextView. I overrided getItemViewType() and getViewTypeCount(), and differentiate when the convertView could be TextView, and view group wrapper, which is 2 View Types. Hence, no NullPointerException at all. But when adding new item, I got ArrayIndexOutOfBound too.user3830579

1 Answers

0
votes

I didn't check in depth why MergeAdapter.getView() recycled convertView as TextView in my custom adapter, so when my row view is not null and those cached view holder with ImageViews, Buttons,and TextViews can't be properly rendered in this regard. I know there must be something with getItemViewType() and getViewTypeCount(). I did that, and added some conditions to see if it's header textview or outer wrapper type like RelativeLayout. It looks fine at the beginning, but sometimes When a new content is generated in a new section, which means a new adapter will be added in MergeAdapter, there can be once again an ArrayIndexOutOfBoundException. I know the critical issue must be due to a combo of a bit complicated items in one row of the listview, as the above resource file indicates. I'm not sure how getViewTypeCount() and getItemViewType() could be overridden in this scenario. Any idea of this case? Thanks.