1

I have a listview with a custom CursorAdapter and 2 layout types. In onCreate I populate the listview with data from a local database using only one layout type. Then I download some data from the internet and add a new row to the TOP of the listview (using the following method: How to insert extra elements into a SimpleCursorAdapter or Cursor for a Spinner?) with a different layout. I assume the adapter reuses the old layout because the new layout does not get inflated and the app crashes with a NullPointerException when populating the row in bindView (I don't use a viewholder for the second layout type because there is only one row of it). How can I solve this problem? Can I possibly force the adapter to "reinflate" views somehow?

Here is how my code roughly looks like:

The Fragment:

public class MyFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>{

    private ListView mListView;
    private CustomCursorAdapter cursorAdapter;

    public MyFragment() {
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        getLoaderManager().initLoader(0, null, this);
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        mListView = (ListView)inflater.inflate(R.layout.fragment, container, false);

        cursorAdapter = new CustomCursorAdapter(getActivity(), null, 0);
        mListView.setAdapter(cursorAdapter);

        return mListView;
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return new CursorLoader(
                getActivity(),
                CONTENT_URI,
                projection,
                selection,
                selectionArgs,
                sortOrder
        );
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        cursorAdapter.swapCursor(data);
        new SomeTask().execute(/*params*/);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        cursorAdapter.swapCursor(null);
    }

    private class SomeTask extends AsyncTask<String, Void, String>{

        @Override
        protected String doInBackground(String... params) {
            //Download some stuff
        }

        @Override
        protected void onPostExecute(String s) {
            //Add downloaded stuff to cursor
            cursorAdapter.swapCursor(extendedCursor);
        }
    }
}

The Adapter:

public class CustomCursorAdapter extends CursorAdapter {

    private static final int VIEW_TYPE_COUNT = 2;
    private static final int VIEW_TYPE_ONE = 0;
    private static final int VIEW_TYPE_TWO = 1;

    public CustomCursorAdapter(Context context, Cursor c, int flags) {
        super(context, c, flags);
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        int viewType = getItemViewType(cursor.getPosition());
        View view;
        switch (viewType) {
            case VIEW_TYPE_ONE: {
                view = LayoutInflater.from(context).inflate(R.layout.layout_one, parent, false);

                ViewHolder viewHolder = new ViewHolder(view);
                view.setTag(viewHolder);
            }
            case VIEW_TYPE_TWO: {
                view = LayoutInflater.from(context).inflate(R.layout.layout_two, parent, false);
            }
        }
        return view;
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        int viewType = getItemViewType(cursor.getPosition());
        switch (viewType){
            case VIEW_TYPE_ONE:
                // populate view using the viewholder
                break;
            case VIEW_TYPE_TWO:
                // populate newly added row, ((TextView)findViewById).setText() throws NullPointerException
                break;
        }
    }

    @Override
    public int getItemViewType(int position) {
        return /*is the new view added*/ && position == 0 ? VIEW_TYPE_TWO : VIEW_TYPE_ONE;
    }

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

    public static class ViewHolder {

        //cache views here

        public ViewHolder(View view) {
        }
    }
}
Community
  • 1
  • 1
Longi
  • 3,913
  • 2
  • 28
  • 38

2 Answers2

1

You forgot the break statements for your switch in newView method.

Ifrit
  • 6,791
  • 8
  • 50
  • 79
  • And I did, indeed. Sometimes I need a pat on the back to remind me how special I am :) I constantly forget those... – Longi Jan 10 '15 at 00:13
0

One way to do it would be to use a single view containing all the elements for both views, and for each element use .setEnabled(boolean) and/or .setVisibility(int) to control which ones are shown.

pgs
  • 13,385
  • 2
  • 25
  • 17
  • Yes, the code gould get a bit ugly. You may be able to clean it up by using a viewGroup of some sort. The on screen display should still look how you want it to. – pgs Jan 09 '15 at 23:59