title: ListView filter: IllegalStateException
body: <p>For the sake of sanity I am posting this here, because I am sure I cannot solve it on my own now. I am posting everything there is to know, because I think why I got no solution to this problem in the past is because I did not post everything. I have stripped the code so far that basically it is as light as it can get. But still this error occurs (although very very very rarely):</p>
  
  <pre><code>03-27 10:57:27.783: E/java.lang.IllegalStateException(4929): The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(16908298, class android.widget.ListView) with Adapter(class de.innosoft.android.mobileserviceclient.activities.ListEinsaetze$ArrayAdapterEinsatz)]
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929): java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(16908298, class android.widget.ListView) with Adapter(class de.innosoft.android.mobileserviceclient.activities.ListEinsaetze$ArrayAdapterEinsatz)]
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.ListView.layoutChildren(ListView.java:1544)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.AbsListView.onLayout(AbsListView.java:1994)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.AdapterView.updateEmptyStatus(AdapterView.java:747)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.AdapterView.checkFocus(AdapterView.java:720)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.AdapterView$AdapterDataSetObserver.onChanged(AdapterView.java:812)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.AbsListView$AdapterDataSetObserver.onChanged(AbsListView.java:5958)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.BaseAdapter.notifyDataSetChanged(BaseAdapter.java:50)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at de.innosoft.android.mobileserviceclient.ui.DynamicArrayAdapter.notifyDataSetChanged(DynamicArrayAdapter.java:291)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at de.innosoft.android.mobileserviceclient.ui.DynamicArrayAdapter.notifyDataSetChanged(DynamicArrayAdapter.java:268)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at de.innosoft.android.mobileserviceclient.ui.DynamicArrayAdapter$DynamicAdapterFilter.publishResults(DynamicArrayAdapter.java:119)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.Filter$ResultsHandler.handleMessage(Filter.java:282)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.os.Handler.dispatchMessage(Handler.java:99)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.os.Looper.loop(Looper.java:137)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.app.ActivityThread.main(ActivityThread.java:5039)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at java.lang.reflect.Method.invokeNative(Native Method)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at java.lang.reflect.Method.invoke(Method.java:511)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
  03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at dalvik.system.NativeStart.main(Native Method)
  </code></pre>
  
  <p>At the very bottom of this post I explain WHEN this error occurs. But first, the code. I have this TextWatcher:</p>
  
  <pre><code>edSearch.addTextChangedListener(new TextWatcher() {
                  @Override
                  public void onTextChanged(CharSequence s, int start, int before, int count) {
                  }
  
                  String text = null;
  
                  @Override
                  public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                      text = s.toString();
                  }
  
                  @Override
                  public void afterTextChanged(final Editable s) {
                      if (text != null &amp;&amp; !text.equals(s.toString())) {
                          adapter.getFilter().filter(s.toString());
                      }
                      text = null;
                  }
              });
  </code></pre>
  
  <p>This thread is run on the sideline to trigger the textwatcher every 200 milliseconds. I'd be crazy to test this manually:</p>
  
  <pre><code>new Thread(new Runnable() {
              public void run() {
                  Runnable r = new Runnable() {
                      public void run() {
                           dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN,
                           KeyEvent.KEYCODE_S));
                           dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
                           KeyEvent.KEYCODE_S));
                      }
                  };
  
                  Runnable r2 = new Runnable() {
                      public void run() {
                           dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN,
                           KeyEvent.KEYCODE_DEL));
                           dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
                           KeyEvent.KEYCODE_DEL));
                      }
                  };
                  while (true) {
  
                      runOnUiThread(r);
                      try {
                          Thread.sleep(200);
                      } catch (InterruptedException e) {
                          ExceptionHandler.handle(e);
                      }
                      runOnUiThread(r2);
                      try {
                          Thread.sleep(200);
                      } catch (InterruptedException e) {
                          ExceptionHandler.handle(e);
                      }
                  }
              }
          }).start();
      }
  </code></pre>
  
  <p>This is my Adapter:</p>
  
  <pre><code>private class ArrayAdapterEinsatz extends DynamicArrayAdapter&lt;Einsatz&gt; {
  
          public ArrayAdapterEinsatz(int textViewResourceId) {
              super(ListEinsaetze.this, textViewResourceId, new ArrayList&lt;Einsatz&gt;());
          }
  
          @Override
          public View getView(final int position, View convertView, ViewGroup parent) {
  
              Einsatz einsatz = getFilteredItems().get(position);
  
              View rowView = convertView;
              if (rowView == null) {
                  LayoutInflater inflater = ListEinsaetze.this.getLayoutInflater();
                  rowView = inflater.inflate(R.layout.eintrag_einsatz, null);
                  ViewHolder viewHolder = new ViewHolder();
  
                  viewHolder.tvId = (TextView) rowView.findViewById(R.id.eeinsatzid);
                  viewHolder.tvDatumVonbis = (TextView) rowView.findViewById(R.id.eeinsatzvonbis);
                  viewHolder.tvKundenName = (TextView) rowView.findViewById(R.id.eeinsatzkundenname);
                  viewHolder.tvKundenOrt = (TextView) rowView.findViewById(R.id.eeinsatzort);
                  viewHolder.tvEntfernung = (TextView) rowView.findViewById(R.id.eeinsatzentfernung);
                  viewHolder.iveRotate = (ImageViewEffects) rowView.findViewById(R.id.eeinsatzrotate);
                  viewHolder.iveRotate.setBitmap(Utils.drawableToBitmap(R.drawable.ic_refresh_1, 22), ImageViewEffects.ROTATE);
                  viewHolder.ivAnsprechpartnerVorhanden = (ImageView) rowView.findViewById(R.id.eeinsatzimageansprechvorhanden);
                  viewHolder.ivBelegeVorhanden = (ImageView) rowView.findViewById(R.id.eeinsatzimagebelegevorhanden);
                  viewHolder.ivTaetigkeitVorhanden = (ImageView) rowView.findViewById(R.id.eeinsatzimagetaetigkeitvorhanden);
  
                  rowView.setTag(viewHolder);
              }
  
              final ViewHolder holder = (ViewHolder) rowView.getTag();
  
              return rowView;
          }
  
      }
  </code></pre>
  
  <p>And finally this is the base adapter class. Notice the actually filter function at the very bottom, it filters according to new Random().nextBoolean() which cannot be any simpler.</p>
  
  <pre><code>public class DynamicArrayAdapter&lt;T&gt; extends BaseAdapter implements Filterable {
  
    public class DynamicAdapterFilter extends Filter {
  
      private CharSequence m_Filter = null;
      private List&lt;T&gt; m_FilteredItems = null;
  
      public DynamicAdapterFilter() {
        m_FilteredItems = new ArrayList&lt;T&gt;();
      }
  
      @Override
      protected FilterResults performFiltering(CharSequence constraint) {
  
  
        FilterResults r = new FilterResults();
        List&lt;T&gt; items = null;
        m_Filter = constraint;
  
        if (constraint == null) {
          items = m_AllItems;
        } else {
          items = m_FilteredItems;
          items.clear();
  
          synchronized (SyncLock) {
            List&lt;T&gt; l = new ArrayList&lt;T&gt;(m_AllItems);
            for (T item : l) {
              if (DynamicArrayAdapter.this.filter(item, constraint)) {
                items.add(item);
              }
            }
          }
        }
  
        r.values = items;
        r.count = items.size();
  
        return r;
      }
  
      @SuppressWarnings("unchecked")
      @Override
      protected void publishResults(CharSequence constraint, FilterResults results) {
  
        m_Items = (List&lt;T&gt;) results.values;
  
        notifyDataSetChanged();
  
      }
  
      public void refresh() {
        if (m_Filter != null) {
  
          filter(m_Filter);
        }
      }
  
      public void refresh(FilterListener listener) {
        if (m_Filter != null ) {
  
          filter(m_Filter, listener);
        }
      }
    }
  
  
    private final Object SyncLock = new Object();
    private Context m_Context = null;
    private LayoutInflater m_Inflater = null;
    private int m_DelegateResourceId;
    private boolean m_ChangeNotificationsEnabled = true;
    private List&lt;T&gt; m_AllItems = null;
    private List&lt;T&gt; m_Items = null;
    private DynamicAdapterFilter m_Filter = null;
  
  
    public DynamicArrayAdapter(Context context, int delegateResourceId) {
      this(context, delegateResourceId, new ArrayList&lt;T&gt;());
    }
  
    public DynamicArrayAdapter(Context context, int delegateResourceId, List&lt;T&gt; items) {
      m_Context = context;
      m_DelegateResourceId = delegateResourceId;
      initItems(items);
  
      m_Inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  
    }
  
    public void initItems(List&lt;T&gt; items) {
      m_AllItems = m_Items = items;
    }
  
    /**
     * returns the filtered item count (or all item count if no filter exists)
     */
    @Override
    public int getCount() {
          return m_Items != null ? m_Items.size() : 0;
    }
  
    /**
     * returns the item at the position in the filtered list (or all item count if
     * no filter exists)
     */
    @Override
    public T getItem(int position) {
      return m_Items.get(position);
    }
  
    /**
     * holt position von item, exakte gleiche implementation wie ArrayAdapter&lt;T&gt;
     * 
     * @param item
     * @return
     */
    public int getPosition(T item) {
      return m_Items.indexOf(item);
    }
  
    /**
     * {@inheritDoc}
     */
    @Override
    public long getItemId(int position) {
  
      return position;
    }
  
    /**
     * returns the list view item delegate view. this view is inflated if it isn't
     * already inflated. Override this method to perform custom view operations
     * for the list view item.
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      if (convertView == null) {
        convertView = m_Inflater.inflate(m_DelegateResourceId, parent, false);
      }
  
      return convertView;
    }
  
    /**
     * returns the filter. this filter is created if it hasn't yet been created.
     */
    @Override
    public DynamicAdapterFilter getFilter() {
      if (m_Filter == null) {
        m_Filter = new DynamicAdapterFilter();
      }
  
      return m_Filter;
    }
  
    /**
     * If notifications are enabled, this notifies the base adapter that the
     * backing data has changed
     */
    @Override
    public void notifyDataSetChanged() {
      notifyDataSetChanged(false);
    }
  
    /**
     * If forced or notifications are enabled, this notifies the base adapter that
     * the backing data has changed
     * 
     * @param force
     *          force notify even if notifications are disabled
     */
    public void notifyDataSetChanged(boolean force) {
      if (force || m_ChangeNotificationsEnabled) {
  
        super.notifyDataSetChanged();
      }
    }
  
    /**
     * @return UI context
     */
    public Context getContext() {
      return m_Context;
    }
  
    /**
     * @return UI inflater
     */
    public LayoutInflater getLayoutInflater() {
      return m_Inflater;
    }
  
    /**
     * @return Layout ID of the list view item delegate
     */
    public int getDelegateResourceId() {
      return m_DelegateResourceId;
    }
  
    /**
     * @return true if notifications are enabled, false otherwise
     */
    public boolean getChangeNotificationsEnabled() {
      return m_ChangeNotificationsEnabled;
    }
  
    /**
     * Set whether notifications are enabled or not
     */
    public void setChangeNotificationsEnabled(boolean value) {
      m_ChangeNotificationsEnabled = value;
    }
  
    // internal mechanism to handle automagical updates of the list items
    private void onDataChange() {
  
  
      if (m_Filter == null) {
        notifyDataSetChanged();
      } else {
        m_Filter.refresh();
      }
    }
  
    /**
     * @return All (unfiltered) items
     */
    public List&lt;T&gt; getAllItems() {
      return m_AllItems;
    }
  
    /**
     * Set the backing list
     */
    public void setAllItems(List&lt;T&gt; value) {
      m_AllItems = value;
  
      onDataChange();
    }
  
    /**
     * @return Current filtered items, or all items if there is no filter
     */
    public List&lt;T&gt; getFilteredItems() {
      return m_Items;
    }
  
    public void add(T item) {
      synchronized (SyncLock) {
        m_AllItems.add(item);
      }
  
      onDataChange();
    }
  
    public void insert(T item, int index) {
      synchronized (SyncLock) {
        m_AllItems.add(index, item);
      }
  
      onDataChange();
    }
  
    public void remove(T item) {
      synchronized (SyncLock) {
        m_AllItems.remove(item);
      }
  
      onDataChange();
    }
  
    public void removeAt(int index) {
      synchronized (SyncLock) {
        m_AllItems.remove(index);
      }
  
      onDataChange();
    }
  
    public void clear() {
      synchronized (SyncLock) {
        m_AllItems.clear();
      }
  
      onDataChange();
    }
  
    public void sort(Comparator&lt;? super T&gt; comparator) {
      synchronized (SyncLock) {
        Collections.sort(m_Items, comparator);
      }
  
      onDataChange();
    }
  
    protected boolean filter(T item, CharSequence constraint) { // AR
      return new Random().nextBoolean();
  
    }
  }
  </code></pre>
  
  <p>So basically the program just triggers the textwatcher every 200 ms. So the textwatcher filters the list again and again. Nothing else is happining in the program.</p>
  
  <p>If I set no breakpoint the program just runs fine with no error always.</p>
  
  <p>The error occurs when I breakpoint the line</p>
  
  <pre><code>adapter.getFilter().filter(s.toString());
  </code></pre>
  
  <p>in the TextWatcher and hold F8 (continue) pressed (this seems to bring irregularity into the program flow, I can only imagine?). Without breakpoint no error occurs.</p>
  
  <p>And BTW if I put</p>
  
  <pre><code>adapter.notifyDatasetChanged()
  </code></pre>
  
  <p>after</p>
  
  <pre><code>adapter.getFilter().filter(s.toString());
  </code></pre>
  
  <p>the same error will occur 100% of the time (I know notifyDatasetChanged is called in publishResults, so I don
  t need it, but is it nonetheless correct behaviour?).</p>
  
  <p><strong>TL;DR:</strong></p>
  
  <p>Is my code (especially the base adapter code) failsafe? (well can't be else no errors would occur)</p>
  
  <p><strong>EDIT:</strong></p>
  
  <p>error also occurs when no breakpoint is set I just got one...</p>