Android ListView Adapter error when calling notifyDataSetChanged, Android error?
In the application I was working on, I have a custom class DeviceListAdapter
extending BaseAdapter
that is passed to mine ListView
. In my class, DeviceListAdapter
I keep my own ArrayList<Device>
, which I use to create list views with View getView(... )
. Whenever the application calls for a data change, I use the custom methods in DeviceListAdapter
to update ArrayList<Device>
to reflect the changes. I used a debugger and lots of print statements to check that the data will change as I expect by adding and removing objects Device
as indicated. However, after every data change, I also call notifyDataSetChanged()
, but in the UI, none of the items are updated. In the debugger, I found that after calling the notifyDataSetChanged()
methodgetView(... )
was not called, which explains why it was ListView
not redrawn. To figure out why, I used the step-in debugger feature to track where program execution fell into the Android framework since I have the SDK source loaded. What I found was very interesting. The execution path was as follows:
DeviceListAdapter.notifyDataSetChanged() BaseAdapter.notifyDataSetChanged() DataSetObservable.notifyChanged() AbsListView.onInvalidated()
Rather a method call onChanged()
, it jumped and executed the method onInvalidated()
after reaching AbsListView
. I originally thought it was a bug where the debugger might be reading the wrong line number, but I restarted my android studio and also completely uninstalled and reinstalled the app, but the result is the same. Can anyone tell me if this is a legitimate issue for the Android platform or if the debugger is not reliable for tracking execution outside of my own project files?
More on my implementation notifyDataSetChanged()
... I created a local override method BaseAdapter
notifyDataSetChanged()
so that I can set a boolean flag mForceRedraw
inside mine DeviceListAdapter
as to whether I should force redrawing my list entries. In the method, getView(... )
I usually check if the second parameter is View convertView
null, if that's what I'm redrawing the view, and if not, then I pass the convertView and return it. However, when "mForceRedraw" is true, I never return convertView
, I am explicitly redrawing the view. The issue that comes up is due to my previous concern, which is getView()
not being called after execution notifyDataSetChanged()
.
EDIT: Here's a snippet of my code DeviceListAdapter
:
/**
* Serves data about current Device data to the mDeviceListView. Manages the dynamic and
* persistent storage of the configured Devices and constructs views of each individual
* list item for placement in the list.
*/
private class DeviceListAdapter extends BaseAdapter {
private boolean mForceRedraw = false;
/**
* Dynamic array that keeps track of all devices currently being managed.
* This is held in memory and is readily accessible so that system calls
* requesting View updates can be satisfied quickly.
*/
private List<Device> mDeviceEntries;
private Context mContext;
public DeviceListAdapter(Context context) {
this.mContext = context;
this.mDeviceEntries = new ArrayList<>();
populateFromStorage();
}
/**
* Inserts the given device into storage and notifies the mDeviceListView of a data update.
* @param newDevice The device to add to memory.
*/
public void put(Device newDevice) {
Preconditions.checkNotNull(newDevice);
boolean flagUpdatedExisting = false;
for (Device device : mDeviceEntries) {
if (newDevice.isVersionOf(device)) {
int index = mDeviceEntries.indexOf(device);
if(index != -1) {
mDeviceEntries.set(index, newDevice);
flagUpdatedExisting = true;
break;
} else {
throw new IllegalStateException();
}
}
//If an existing device was not updated, then this is a new device, add it to the list
if (!flagUpdatedExisting) {
mDeviceEntries.add(newDevice);
}
TECDataAdapter.setDevices(mDeviceEntries);
notifyDataSetChanged();
}
/**
* If the given device exists in storage, delete it and remove it from the mDeviceListView.
* @param device
*/
public void delete(Device device) {
Preconditions.checkNotNull(device);
//Remove device from mDeviceEntries
Iterator iterator = mDeviceEntries.iterator();
while(iterator.hasNext()) {
Device d = (Device) iterator.next();
if(device.isVersionOf(d)) {
iterator.remove();
}
}
TECDataAdapter.setDevices(mDeviceEntries);
notifyDataSetChanged();
}
/**
* Retrieves Device entries from persistent storage and loads them into the dynamic
* array responsible for displaying the entries in the listView.
*/
public void populateFromStorage() {
List<Device> temp = Preconditions.checkNotNull(TECDataAdapter.getDevices());
mDeviceEntries = temp;
notifyDataSetChanged();
}
public int getCount() {
if (mDeviceEntries != null) {
return mDeviceEntries.size();
}
return 0;
}
public Object getItem(int position) {
return mDeviceEntries.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(final int position, View convertView, ViewGroup parent) {
LinearLayout view;
if (convertView == null || mForceRedraw) //Regenerate the view
{
/* Draws my views */
} else //Reuse the view
{
view = (LinearLayout) convertView;
}
return view;
}
@Override
public void notifyDataSetChanged() {
mForceRedraw = true;
super.notifyDataSetChanged();
mForceRedraw = false;
}
}
source to share
You are in the adapter and trigger the dataset notification. It would ideally not even be necessary. Since you are modifying the dataset that is used internally by your adapter. Your adapter's getView method will always be called whenever the view needs to be rendered.
The conversion approach is solely about reworking the presentation (not the data). It simply gives you an alternative to the costly process of inflation of views.
So what your code should be:
public View getView(final int position, View convertView, ViewGroup parent) {
LinearLayout view;
if (convertView == null) //Regenerate the view
{
/* inflate Draws my views */
} else
{
view = (LinearLayout) convertView;
}
//repopulate this view with the data that needs to appear at this position using getItem(position)
return view;
}
source to share
There are many errors with, notifyDataSetChanged()
and they usually appear when you try to do complex work with the list data.
This is mainly because the method is lazy and cannot distinguish between changes, so to avoid this problem, test your code with this script:
- remove changing lines
- call
notifyDataSetChanged()
- add changed rows to your indices
- call again
notifyDataSetChanged()
and tell me if your problem solved it.
Edit: After entering the adapter code, I encountered an error in your code.
Sorry for the late reply:
convertView
is the view that you filled out before initializing it.
When getView()
you get an instance in a method convertView
, you must fill it in before returning it. so to be clear, do something like this:
public View getView(final int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) //Regenerate the view
{
/* Instantiate your view */
} else {
view = convertView;
}
// populate the elements in view like EditText, TextView, ImageView and etc
return view;
}
source to share