CWAC EndlessAdapter Users: Upgrade, Please
If you have been using
my EndlessAdapter
,
I strongly encourage you
to upgrade to v1.2.1. There had been various reports of an
exception in the field, but only today was I given a reproducible
test case for it, and that bug should now be fixed.
If you are already on 1.x, there should be no code changes. Just update your Android library project, or grab a fresh JAR.
The error report was for:
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.
Lesson #1: Error messages may be misleading. In this case, the exception points out that this may be a threading problem. However, in my case, it turned out that this was not a threading problem, which led to a couple of lost hours of debugging.
Lesson #2: If you do something in an Adapter
that
changes what getCount()
returns, you need to call
notifyDataSetChanged()
immediately.
In the case of EndlessAdapter
, getCount()
will return the
getCount()
from the Adapter
that it wraps, or that value plus one.
The latter case is when EndlessAdapter
was told that there might
be more data, and so the extra item is a “pending view” used to
denote that there is data being loaded.
EndlessAdapter
tracks whether or not there is data to be loaded
in a keepOnAppending
. I dutifully made this be an AtomicBoolean
,
so it is a thread-safe value. However, what I did not take into account
was that toggling keepOnAppending
between true
and false
would
cause getCount()
to return a different value. Eventually,
notifyDataSetChanged()
would be called, after which I was safe, but
there was a window of time in between there where manipulating the
list — such as tapping on a row — would trigger the
IllegalStateException
.
What that IllegalStateException
really means is that the
ListView
thinks there should be N rows, but the Adapter
is now
claiming that there are M rows, with M != N. These values are
synchronized via a call to notifyDataSetChanged()
, but if
getCount()
changes before notifyDataSetChanged()
is called,
you have the window for potential problem.
My change is simply to call notifyDataSetChanged()
any time
I change the value of keepOnAppending
, thereby triggering
getCount()
to change its return value.
In the field, you would see this problem when the EndlessAdapter
was told that we have no more data (toggling keepOnAppending
from true
to false
) but the user taps on a list item before
we get a chance to append the last chunk of data, which would trigger
a notifyDataSetChanged()
call.
Many thanks to jbenf for reporting the way to reproduce the error, which allowed me to make this fix.