Search code examples
androidandroid-sqlitesimplecursoradapterandroid-cursoradapterandroid-cursor

AutoCompleteTextView with CursorLoader and SimpleCursorAdapter


I try to populate the suggestions list with the data of a db table. However I get StaleDataExceptions. It throws quite randomly, but always when I enter a character into the textview.

Here is my code:

CursorLoader extending Cristian's SimpleCursorLoader class

public class TagCursorLoader extends SimpleCursorLoader {

    private String mSelection;
    private TagDbLoader mDbLoader;

    public TagCursorLoader(Context context, TagDbLoader dBLoader, String selection) {
        super(context);
        this.mDbLoader = dBLoader;
        this.mSelection = selection;
    }

    @Override
    public Cursor loadInBackground() {
        return mDbLoader.fetchContainingString(mSelection);
    }

}

The Loader callbacks:

public class TagCursorLoaderCallback implements LoaderCallbacks<Cursor>, CursorToStringConverter {

    private Context mContext;
    private TagDbLoader mdDbLoader;
    private SimpleCursorAdapter mAdapter;
    private String mSelection;

    public TagCursorLoaderCallback(Context context, TagDbLoader dBLoader, SimpleCursorAdapter adapter) {
        this.mContext = context;
        this.mdDbLoader = dBLoader;
        mAdapter = adapter;
        mSelection = "";
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return new TagCursorLoader(mContext, mdDbLoader, mSelection);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {

        if (!data.isClosed()) {
            mAdapter.swapCursor(data);
        }
    }

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

    public void setSelection(String mSelection) {
        this.mSelection = mSelection;
    }

    @Override
    public CharSequence convertToString(Cursor cursor) {
        return cursor.getString(cursor.getColumnIndexOrThrow(DbConstants.Tags.KEY_TAG));
    }

}

And finally when I set up the AutoCompleteTextView:

private void initializeAutoComplete() {

        mTagDbLoader = new TagDbLoader(getActivity());
        mTagDbLoader.open();

        mTagInput = (AutoCompleteTextView) mLayout.findViewById(R.id.autoComplete);

        mTagInput.addTextChangedListener(new TextWatcherAdapter() {

            @Override
            public void afterTextChanged(Editable s) {
                mLoaderCallback.setSelection(s.toString());
                getLoaderManager().restartLoader(0, null, mLoaderCallback);
            }
        });

        mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_1,
                null, new String[] { DbConstants.Tags.KEY_TAG }, new int[] { android.R.id.text1 },
                0);

        mLoaderCallback = new TagCursorLoaderCallback(getActivity(), mTagDbLoader, mAdapter);
        mAdapter.setCursorToStringConverter(mLoaderCallback);
        mTagInput.setAdapter(mAdapter);
        getLoaderManager().initLoader(0, null, mLoaderCallback);
    }

Solution

  • After some investigation, it seems that SimpleCursorAdapter inherits from ResourceCursorAdapter, which inherits from CursorAdapter. CursorAdapter uses CursorFilter for filtering, and this class calls changeCursor() in its publishResults(). changeCursor closes the old cursor... So that's why my cursors were closed automatically.

    I dropped the loaders, and changed the implementation to the code below, and it works greatly:

    mAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1,
                    mTagDbLoader.fetchAll(), new String[] { DbConstants.Tags.KEY_TAG },
                    new int[] { android.R.id.text1 }, 0);
    
    mAdapter.setFilterQueryProvider(new FilterQueryProvider() {
    
        @Override
        public Cursor runQuery(CharSequence constraint) {
    
            if (constraint == null || constraint.equals(""))
                return mAdapter.getCursor();
    
            return mTagDbLoader.fetchContainingString(constraint.toString());
        }
    });
    
    mAdapter.setCursorToStringConverter(new CursorToStringConverter() {
    
        @Override
        public CharSequence convertToString(Cursor c) {
    
            return c.getString(c.getColumnIndexOrThrow(DbConstants.Tags.KEY_TAG));
        }
    });