title: Why is Android recycling the wrong view type in my SpinnerAdapter?
body: <p>I'm trying to make an ActionBar spinner that has separators. I have implemented a <code>SpinnerAdapter</code> that has 2 item view types (thanks to <code>getViewTypeCount</code>). The problem is that I'm being sent some <code>convertViews</code> from the other type.</p>
  
  <p>Here's my SpinnerAdapter:</p>
  
  <pre><code>public abstract class SeparatorSpinnerAdapter implements SpinnerAdapter {
      Context mContext;
      List&lt;Object&gt; mData;
      int mSeparatorLayoutResId, mActionBarItemLayoutResId, mDropDownItemLayoutResId, mTextViewResId;
  
      public static class SpinnerSeparator {
          public int separatorTextResId;
  
          public SpinnerSeparator(final int resId) {
              separatorTextResId = resId;
          }
      }
  
      public abstract String getText(int position);
  
      public SeparatorSpinnerAdapter(final Context ctx, final List&lt;Object&gt; data, final int separatorLayoutResId, final int actionBarItemLayoutResId,
              final int dropDownItemLayoutResId, final int textViewResId) {
          mContext = ctx;
          mData = data;
          mSeparatorLayoutResId = separatorLayoutResId;
          mActionBarItemLayoutResId = actionBarItemLayoutResId;
          mDropDownItemLayoutResId = dropDownItemLayoutResId;
          mTextViewResId = textViewResId;
      }
  
      protected String getString(final int resId) {
          return mContext.getString(resId);
      }
  
      @Override
      public void registerDataSetObserver(final DataSetObserver observer) {
      }
  
      @Override
      public void unregisterDataSetObserver(final DataSetObserver observer) {
      }
  
      @Override
      public int getCount() {
          if (mData != null) {
              return mData.size();
          }
          return 0;
      }
  
      @Override
      public Object getItem(final int position) {
          return mData == null ? null : mData.get(position);
      }
  
      @Override
      public boolean isEmpty() {
          return getCount() == 0;
      }
  
      @Override
      public long getItemId(final int position) {
          return 0;
      }
  
      @Override
      public boolean hasStableIds() {
          return false;
      }
  
      @Override
      public View getView(final int position, final View convertView, final ViewGroup parent) {
          return getView(mActionBarItemLayoutResId, position, convertView, parent);
      }
  
      public boolean isSeparator(final int position) {
          final Object item = getItem(position);
          if (item != null) {
              return item instanceof SpinnerSeparator;
          }
          return false;
      }
  
      @Override
      public int getItemViewType(final int position) {
          return isSeparator(position) ? 0 : 1;
      }
  
      @Override
      public int getViewTypeCount() {
          return 2;
      }
  
      @Override
      public View getDropDownView(final int position, final View convertView, final ViewGroup parent) {
          return getView(isSeparator(position) ? mSeparatorLayoutResId : mDropDownItemLayoutResId, position, convertView, parent);
      }
  
      private View getView(final int layoutResId, final int position, final View convertView, final ViewGroup parent) {
          View v;
  
          Log.i("TAG", "getView #" + position + "\tVT=" + getItemViewType(position) + "\tCV="
                  + (convertView == null ? " null  " : convertView.getClass().getSimpleName()) + "\ttext=&gt; " + getText(position));
  
          if (convertView == null) {
              final LayoutInflater li = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
              v = li.inflate(layoutResId, parent, false);
          } else {
              v = convertView;
          }
  
          final TextView tv = (TextView) v.findViewById(mTextViewResId);
          if (tv != null) {
              tv.setText(getText(position));
  
              if (isSeparator(position)) {
                  tv.setOnClickListener(null);
                  tv.setOnTouchListener(null);
              }
          }
  
          return v;
      }
  }
  </code></pre>
  
  <p>One implementation:</p>
  
  <pre><code>public class IssuesMainFilterAdapter extends SeparatorSpinnerAdapter {
      public IssuesMainFilterAdapter(final Context ctx, final List&lt;Query&gt; queries, final List&lt;Project&gt; projects) {
          super(ctx, buildDataArray(queries, projects), R.layout.issues_filter_spinner_separator, R.layout.issues_filter_spinner_in_actionbar,
                  R.layout.issues_filter_spinner, R.id.issues_filter_spinner_text);
      }
  
      private static List&lt;Object&gt; buildDataArray(final List&lt;Query&gt; queries, final List&lt;Project&gt; projects) {
          final List&lt;Object&gt; data = new ArrayList&lt;Object&gt;();
          data.add(null); // "ALL"
          data.add(new SpinnerSeparator(R.string.issue_filter_queries));
          data.addAll(queries);
          data.add(new SpinnerSeparator(R.string.issue_filter_projects));
          data.addAll(projects);
          return data;
      }
  
      @Override
      public String getText(final int position) {
          final Object item = getItem(position);
          if (item == null) {
              return getString(R.string.issue_filter_all);
          } else if (item instanceof Query) {
              return ((Query) item).name;
          } else if (item instanceof Project) {
              return ((Project) item).name;
          } else if (item instanceof SpinnerSeparator) {
              return getString(((SpinnerSeparator) item).separatorTextResId);
          }
          throw new InvalidParameterException("Item has unknown type: " + item);
      }
  }
  </code></pre>
  
  <p>As you may have noticed, I have set a log line into <code>getView()</code> so that I better understand what's going on:</p>
  
  <pre><code>05-06 14:01:28.721 I/TAG( 5879): getView #0 VT=1    CV=TextView text=&gt; ####
  05-06 14:01:28.721 I/TAG( 5879): getView #1 VT=0    CV=LinearLayout text=&gt; ####
  05-06 14:01:28.729 I/TAG( 5879): getView #2 VT=1    CV=TextView text=&gt; ####
  05-06 14:01:28.745 I/TAG( 5879): getView #3 VT=1    CV=TextView text=&gt; ####
  05-06 14:01:28.745 I/TAG( 5879): getView #4 VT=0    CV=LinearLayout text=&gt; ####
  05-06 14:01:28.745 I/TAG( 5879): getView #5 VT=1    CV=TextView text=&gt; ####
  05-06 14:01:28.753 I/TAG( 5879): getView #6 VT=1    CV=TextView text=&gt; ####
  05-06 14:01:28.768 I/TAG( 5879): getView #7 VT=1    CV=TextView text=&gt; ####
  05-06 14:01:28.768 I/TAG( 5879): getView #8 VT=1    CV=TextView text=&gt; ####
  05-06 14:01:28.768 I/TAG( 5879): getView #9 VT=1    CV=TextView text=&gt; ####
  05-06 14:01:28.776 I/TAG( 5879): getView #10    VT=1    CV=TextView text=&gt; ####
  05-06 14:01:28.792 I/TAG( 5879): getView #11    VT=1    CV=TextView text=&gt; ####
  05-06 14:01:32.081 I/TAG( 5879): getView #12    VT=1    CV=TextView text=&gt; ####
  05-06 14:01:34.690 I/TAG( 5879): getView #13    VT=1    CV=LinearLayout text=&gt; ####
  05-06 14:01:35.573 I/TAG( 5879): getView #14    VT=1    CV=TextView text=&gt; ####
  05-06 14:01:37.237 I/TAG( 5879): getView #15    VT=1    CV=TextView text=&gt; ####
  </code></pre>
  
  <p>As you may have understood, my layouts for the real items are TextViews, and the separator layout is a LinearLayout.</p>
  
  <p>As you can see, a "real" item (<code>VT=1</code> in the log, see item #13) is being recycling a separator view (<code>CV=LinearLayout</code>). I would have thought that Android would provide a <code>convertView</code> of the same type, so the first separator would be recycled only if a view of the same type (i.e. another separator) would have to be created when scrolling.</p>