Search code examples
androidlistviewbaseadapter

notifyDataSetChanged does not work in the ListView with custom BaseAdapter


I try to create ListView. It contains few custom views created by single xml-template + one view for indicating progress. ProgressView always must be at the bottom of my custom ListView. So, here's the code:

public class SearchActivity extends Activity implements OnXMLDataGetterComplete {

    private ListView mMainLayout;
    private HotelsListAdapter mHotelsListAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_search);

        mHotelsListAdapter = new HotelsListAdapter(); //create my custom adapter
        mHotelsListAdapter.setLoading(true); //sets that my ListView will be show ProgressView at the bottom

        mMainLayout = (ListView) findViewById(R.id.search_activity_layout);
        mMainLayout.setAdapter(mHotelsListAdapter); //set adapter for my ListView

        getData(); //start to receive data from web
    }

    //this function runs after HTTP-query
    public void onGetData(final List data) { //receives list of some data
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Iterator<XMLDataGetter.HotelListXMLParser.HotelListXMLEntry> it = data.iterator();
                while (it.hasNext()) {
                    XMLDataGetter.HotelListXMLParser.HotelListXMLEntry item = it.next();
                    mHotelsListAdapter.setLoading(data.size() != 0);
                    HotelItemView hotel = new HotelItemView(SearchActivity.this); //create some custom view by received data
                    ...
                    mHotelsListAdapter.addItem(hotel); //add this view to the adapter
                }
                mHotelsListAdapter.notifyDataSetChanged(); //THIS NOT WORKS
            }
        }
    }

    public class HotelsListAdapter extends BaseAdapter { //ny custom adapter

        private List<HotelItemView> mItems = new ArrayList<HotelItemView>(); //list, which contains all created custom views
        private boolean mIsLoading = false; //variable, which determines, whether ProgressView will be shown
        private RelativeLayout mLoadingView; //layout of activity

        public HotelsListAdapter() {
            LayoutInflater inflater = (LayoutInflater) ((Context) SearchActivity.this).getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            mLoadingView = (RelativeLayout) inflater.inflate(R.layout.hotel_list_loading, null); //inflate ProgressView
        }

        @Override
        public boolean areAllItemsEnabled() {
            return true;
        }

        @Override
        public boolean isEnabled(int position) {
            return true;
        }

        @Override
        public void registerDataSetObserver(DataSetObserver observer) {

        }

        @Override
        public void unregisterDataSetObserver(DataSetObserver observer) {

        }

        @Override
        public int getCount() {
            if (mIsLoading)
                return mItems.size() + 1; //custom adapter contains children vies + ProgressView
            else
                return mItems.size(); //custom adapter contains only children views
        }

        @Override
        public Object getItem(int position) {
            if (position > mItems.size() - 1)
                return mLoadingView; //return ProgressView
            else
                return mItems.get(position); //return child view
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public boolean hasStableIds() {
            return true;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            return (View) getItem(position);
        }

        @Override
        public int getItemViewType(int position) {
            return 0;
        }

        @Override
        public int getViewTypeCount() {
            return 2;
        }

        @Override
        public boolean isEmpty() {
            return false;
        }

        public void addItem(HotelItemView item) {
            mItems.add(item);
        }

        public void clear() {
            mItems.clear();
        }

        public void setLoading(boolean loading) {
            mIsLoading = loading;
        }

        public boolean isLoading() {
            return mIsLoading;
        }
    }
}

This example with ProgressBarView, but I tried to remove references about it:

    @Override
    public int getCount() {
        return mItems.size(); //custom adapter contains only children views
    }

    @Override
    public Object getItem(int position) {
        return mItems.get(position); //return child view
    }

and notifyDataSetChanged() does not work anyway. What is my problem? I can't to display added views.

I tried to add mMainLayout.invalidateViews() before otifyDataSetChanged(). So I saw my views list, but every three-five times I got exception "The content of the adapter has changed but ListView did not receive a notification". What should I do?

Main thing is I don't need to clear my adapter. Adding child view must be incremental. Also I tried to extend ListAdapter, but it have not such method at all


Solution

  • Maybe don't override the registerDataSetObserver Method of the Adapter.

    Some other hints:

    I'd suggest you to use an ArrayAdapter if theres no reason to not use it, because it will save you some of the base config and handle the list management for you. Also, don't save the Views inside the list to display, but the items holding the data. The ListAdapters can recycle the Views for you, so you don't have one View for each item in your List, but one View for each item that is visible. In the getView Method, if the 'convertView' Parameter is not empty, this view can be reused, so just fill its data and return it. This will increase the performance of your List and decrease the memory consumption greatly.

    For the Async stuff, better use an AsyncTask, which is better integrated with the Android system and does stuff like running things in the UI Thread before and after the background task much easier.