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()

      

enter image description hereenter image description hereenter image description hereenter image description here

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;
        }
    }

      

+3


source to share


2 answers


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;
        }

      

+1


source


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;
}

      

+1


source







All Articles