Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The Archive Base

The Archive Base Logo The Archive Base Logo

The Archive Base Navigation

  • SEARCH
  • Home
  • About Us
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • Add group
  • Groups page
  • Feed
  • User Profile
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Buy Points
  • Users
  • Help
  • Buy Theme
  • SEARCH
Home/ Questions/Q 7656065
In Process

The Archive Base Latest Questions

Editorial Team
  • 0
Editorial Team
Asked: May 31, 20262026-05-31T12:40:25+00:00 2026-05-31T12:40:25+00:00

Situation I have an Activity with an AutoCompleteTextView. As you type, the AutoCompleteTextView finds

  • 0

Situation

I have an Activity with an AutoCompleteTextView. As you type, the AutoCompleteTextView finds matching names from your contacts and displays them in a list. If the device orientation is changed while this list is displayed, the Activity crashes (Error message provided after source code).

Notes

I am developing for ICS 4.0.3 and testing on a Nexus S device. I am trying to follow best practices of using LoaderManager to generate and manage cursors. My understanding is that the LoaderManager should preserve cursor data across an orientation change (http://developer.android.com/guide/topics/fundamentals/loaders.html#callback), but that doesn’t seem to be the case.

Because the CursorAdapter wants me to return the original, unfiltered cursor when the filtering constraint is too small to use, I am:

  1. Saving the cursor as a static variable of the Activity for use when filtering criteria is not met (sorry for improper terminology. I’m a Java newbie).
  2. Preventing the CursorAdapter from closing cursors after it replaces them, unless it confirms that it’s not the original cursor by comparing it with the saved cursor.

The problem appears to be that the onLoadFinished LoaderManager callback is being called after an orientation change, but the cursor it’s passing (the original cursor?) was closed during the reorientation.

If I configure my Activity to manage orientation changes itself by adding the following to the activity declaration in my manifest:

android:configChanges=”orientation|screenSize”

the saved original cursor should be preserved across orientation changes (right?). While the app does not crash, another related problem occurs:

  • If I type a few letter, change the device orientation, and then start deleting letters, once I get down to 1 or 0 letters, LogCat gives me a warning that I’m trying to access a cursor after it’s been closed.

It appears that my original cursor is gone in this case also. I’m guessing the app didn’t crash because the onLoadFinished callback is not called when my activity is configured to manage orientation changes itself

My Questions

  1. Am I right in assuming my cursor is being destroyed when device orientation changes?
  2. How can a preserve a cursor and/or its data when device orientation changes?

Source Code

View – home.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <AutoCompleteTextView
        android:id="@+id/newPlayer_edit"
        android:inputType="text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:hint="Contact"
        android:singleLine="true" >
        <requestFocus />
    </AutoCompleteTextView>

</LinearLayout>

Activity – Home.java

public class Home extends Activity implements LoaderManager.LoaderCallbacks<Cursor> {

// Constants
private static final String TAG = "HOME";
private static final boolean DEBUG = true;
public static final int LOADER_CONTACTS_CURSOR = 1;

// Variables
private AdapterContacts adapter;
public static Cursor originalCursor = null;


/**
 * Overrides
 */

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Set the view
    setContentView(R.layout.home);

    // Initialize CursorAdapter
    adapter = new AdapterContacts(this, null, 0);

    // Attach CursorAdapter to AutoCompleteTextView field
    AutoCompleteTextView field = (AutoCompleteTextView) findViewById(R.id.newPlayer_edit);
    field.setAdapter(adapter);

    // Initialize Cursor using LoaderManager
    LoaderManager.enableDebugLogging(true);
    getLoaderManager().initLoader(LOADER_CONTACTS_CURSOR, null, this);
}

@Override
public void onDestroy() {
    if (DEBUG) Log.i(TAG, "Destroying activity");
    super.onDestroy();
}

@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    if (DEBUG) Log.i(TAG, "Loader Callback: creating loader");
    return new CursorLoader(this, ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
}

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    if (DEBUG) Log.i(TAG, "Loader Callback: load finished");
    // If no cursor has been loaded before, reserve this cursor as the original
    // It will be returned by the CursorAdapter when the filter constraint is null 
    if (originalCursor == null) {
        originalCursor = cursor;
    }

    // add the cursor to the adapter
    adapter.swapCursor(cursor);
}

@Override
public void onLoaderReset(Loader<Cursor> loader) {
    if (DEBUG) Log.i(TAG, "Loader Callback: resetting");
    adapter.swapCursor(null);
}
}

CursorAdapter – AdapterContacts.java

public class AdapterContacts extends CursorAdapter {

// Constants
private static final String TAG = "AdapterContacts";
private static final boolean DEBUG = true;

// Variables
private TextView mName;
private ContentResolver mContent;

/**
 * Constructor
 */
public AdapterContacts(Context context, Cursor c, int flags) {
    super(context, c, flags);
    mContent = context.getContentResolver();
}

/**
 * Overrides
 */

@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
    // Inflate the views that create each row of the dropdown list
    final LayoutInflater inflater = LayoutInflater.from(context);
    final LinearLayout ret = new LinearLayout(context);
    ret.setOrientation(LinearLayout.VERTICAL);

    mName = (TextView) inflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
    ret.addView(mName, new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));

    /*
    int nameIdx = cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY);
    mName.setText(cursor.getString(nameIdx));
    */

    return ret;
}

@Override
public void bindView(View view, Context context, Cursor cursor) {
    // Fill the dropdown row with data from the cursor
    int nameIdx = cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY);
    String name = cursor.getString(nameIdx);
    ((TextView) ((LinearLayout) view).getChildAt(0)).setText(name);
}

@Override
public String convertToString(Cursor cursor) {
    // Convert the dropdown list entry that the user clicked on
    // into a string that will fill the AutoCompleteTextView
    int nameCol = cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY);
    return cursor.getString(nameCol);
}

@Override
public void changeCursor(Cursor newCursor) {
    // Because a LoaderManager is used to initialize the originalCursor
    // changeCursor (which closes cursors be default when they're released)
    // is overridden to use swapCursor (which doesn't close cursors). 
    Cursor oldCursor = swapCursor(newCursor);

    // Any swapped out cursors that are not the original cursor must 
    // then be closed manually.
    if (oldCursor != Home.originalCursor) {
        oldCursor.close();
    }
}

@Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
    // If their is a constraint, generate and return a new cursor
    if (constraint != null) {
        // I'd love to use a LoaderManager here too,
        // but haven't quite figured out the best way.
        if (DEBUG) Log.i(TAG, "Constraint is not null: " + constraint.toString());
        Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI, constraint.toString());
        return mContent.query(uri, null, null, null, null);
    }

    // If no constraint, return the originalCursor
    if (DEBUG) Log.i(TAG, "Constraint is null");
    return Home.originalCursor;
}
}

Error Message

03-16 10:39:34.839: E/AndroidRuntime(22097): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.myapp.basic/com.myapp.basic.Home}: android.database.StaleDataException: Attempted to access a cursor after it has been closed.
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1956)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1981)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3351)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.app.ActivityThread.access$700(ActivityThread.java:123)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1151)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.os.Handler.dispatchMessage(Handler.java:99)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.os.Looper.loop(Looper.java:137)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.app.ActivityThread.main(ActivityThread.java:4424)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at java.lang.reflect.Method.invokeNative(Native Method)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at java.lang.reflect.Method.invoke(Method.java:511)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at dalvik.system.NativeStart.main(Native Method)
03-16 10:39:34.839: E/AndroidRuntime(22097): Caused by: android.database.StaleDataException: Attempted to access a cursor after it has been closed.
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.database.BulkCursorToCursorAdaptor.throwIfCursorIsClosed(BulkCursorToCursorAdaptor.java:75)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.database.BulkCursorToCursorAdaptor.getColumnNames(BulkCursorToCursorAdaptor.java:170)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.database.AbstractCursor.getColumnIndex(AbstractCursor.java:248)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.database.AbstractCursor.getColumnIndexOrThrow(AbstractCursor.java:265)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.database.CursorWrapper.getColumnIndexOrThrow(CursorWrapper.java:78)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.widget.CursorAdapter.swapCursor(CursorAdapter.java:338)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at com.myapp.basic.Home.onLoadFinished(Home.java:70)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at com.myapp.basic.Home.onLoadFinished(Home.java:1)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:438)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.app.LoaderManagerImpl$LoaderInfo.finishRetain(LoaderManager.java:309)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.app.LoaderManagerImpl.finishRetain(LoaderManager.java:765)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.app.Activity.performStart(Activity.java:4485)
03-16 10:39:34.839: E/AndroidRuntime(22097):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1929)
03-16 10:39:34.839: E/AndroidRuntime(22097):    ... 12 more

Warning Message – when Activity is configured to manage orientation changes itself

03-16 10:47:50.804: W/Filter(22739): An exception occured during performFiltering()!
03-16 10:47:50.804: W/Filter(22739): android.database.StaleDataException: Attempted to access a cursor after it has been closed.
03-16 10:47:50.804: W/Filter(22739):    at android.database.BulkCursorToCursorAdaptor.throwIfCursorIsClosed(BulkCursorToCursorAdaptor.java:75)
03-16 10:47:50.804: W/Filter(22739):    at android.database.BulkCursorToCursorAdaptor.getCount(BulkCursorToCursorAdaptor.java:81)
03-16 10:47:50.804: W/Filter(22739):    at android.database.CursorWrapper.getCount(CursorWrapper.java:57)
03-16 10:47:50.804: W/Filter(22739):    at android.widget.CursorFilter.performFiltering(CursorFilter.java:53)
03-16 10:47:50.804: W/Filter(22739):    at android.widget.Filter$RequestHandler.handleMessage(Filter.java:234)
03-16 10:47:50.804: W/Filter(22739):    at android.os.Handler.dispatchMessage(Handler.java:99)
03-16 10:47:50.804: W/Filter(22739):    at android.os.Looper.loop(Looper.java:137)
03-16 10:47:50.804: W/Filter(22739):    at android.os.HandlerThread.run(HandlerThread.java:60)
  • 1 1 Answer
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

1 Answer

  • Voted
  • Oldest
  • Recent
  • Random
  1. Editorial Team
    Editorial Team
    2026-05-31T12:40:26+00:00Added an answer on May 31, 2026 at 12:40 pm

    I found the solution (a solution) to my problem came in two parts:

    1. I couldn’t continue generating cursors manually in my CursorAdapter. I had to start using the Loader Manager
    2. I didn’t need to hang on to any cursors.

    Note: For anyone following, I’m still getting a bit of an error while running this code, but it’s not fatal and it doesn’t seem to be related to cursors, so I’m not addressing it here.

    Generating Cursors within the CursorAdapter

    The big complication is that the runQueryOnBackgroundThread method in the CursorAdapter requires that a cursor be returned. When using LoaderManager’s, you don’t get your hands on a cursor until an asynchronous callback, and that has downsides:

    1. You can’t generate and retrieve the cursor from within the runQueryOnBackgroundThread method.
    2. You can’t generate the cursor early because runQueryOnBackgroundThread is the first method called with the new filtering constraint.
    3. The runQueryOnBackgroundThread method hands its cursor off to the changeCursor method, which closes the cursors that are changed out (something we don’t do when working with a LoaderManager/CursorLoader), so we don’t want to follow that workflow anyway.

    By default, the runQueryOnBackgroundThread method of the CursorAdapter just calls the runQuery method of the CursorAdapter’s FilterQueryProvider if it has been defined. I opted to define the FilterQueryProvider rather than override the runQueryOnBackgroundThread method for a few reasons:

    • The FilterQueryProvider could be defined from the Activity that instantiated the CursorAdapter, and utilizing the LoaderManager from the Activity was easier that utilizing it from within the CursorAdapter.
    • I prefer to let the code do what it was meant to do, if at all possible.

    Note: The runQuery method still requires that a cursor be returned, so we don’t get to skirt that issue.

    I decided to generate a dummy cursor in my FilterQueryProvider’s runQuery method. Then, since that dummy cursor would be handed off to the CursorAdapter’s changeCursor method, I overrode changeCursor to simply close every cursor it was passed.

    The runQuery method also initiates the asynchronous LoaderManager call that includes the filtering constraint. Then the LoaderManager callbacks take care of swapping in the new cursors that are generated.

    Note: Ideally, I suppose you could override the function that calls runQueryOnBackgroundThread, and have it make the asynchronous LoaderManager call, but I couldn’t figure out what that was.

    Hanging on to Cursors

    I was trying to distinguish between unfiltered and filtered cursors to the unfiltered cursor could be used when the filtering constraint was null. After reading Android 3.0 – what are the advantages of using LoaderManager instances exactly? for the umpteenth time, I realized that the accepted answer was using the same CursorLoader to generate all cursors.

    Instead of trying to hang on to the original unfiltered cursor, I decided I’d just generate a new unfiltered cursor whenever I needed it. The onCreateLoader LoaderManager callback became a bit more complex (but more like the examples I had been seeing), and the onLoadFinished callback became much more simple (like the examples I had been seeing).

    Source Code

    Activity – home.java

    public class Home extends Activity implements LoaderManager.LoaderCallbacks<Cursor> {
    
    // Constants
    private static final String TAG = "Home";
    private static final boolean DEBUG = true;
    public static final int LOADER_CONTACTS_CURSOR = 1;
    
    // Variables
    private AdapterContacts adapter;
    
    
    
    /**
     * Overrides
     */
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        // Set the view
        setContentView(R.layout.home);
    
        // Initialize CursorAdapter
        adapter = new AdapterContacts(this, null, 0);
        final LoaderManager.LoaderCallbacks<Cursor> iFace = this;
        adapter.setFilterQueryProvider(new FilterQueryProvider() {
            public Cursor runQuery(CharSequence constraint) {
                if (constraint != null) {
                    Bundle bundle = new Bundle();
                    bundle.putCharSequence("constraint", constraint);
                    getLoaderManager().restartLoader(Home.LOADER_CONTACTS_CURSOR, bundle, iFace);
                } else {
                    getLoaderManager().restartLoader(Home.LOADER_CONTACTS_CURSOR, null, iFace);
                }
                return getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
            }
        });
    
        // Attach CursorAdapter to AutoCompleteTextView field
        AutoCompleteTextView field = (AutoCompleteTextView) findViewById(R.id.newPlayer_edit);
        field.setAdapter(adapter);
    
        // Initialize Cursor using LoaderManagers
        LoaderManager.enableDebugLogging(true);
        getLoaderManager().initLoader(LOADER_CONTACTS_CURSOR, null, this);
    }
    
    @Override
    public void onDestroy() {
        if (DEBUG) Log.i(TAG, "Destroying activity");
        super.onDestroy();
    }
    
    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        if (DEBUG) Log.i(TAG, "Loader Callback: creating loader");
        Uri baseUri;
    
        if (args != null) {
            CharSequence constraint = args.getCharSequence("constraint");
            if (DEBUG) Log.i(TAG, "Constraint: " + constraint.toString());
            baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI, Uri.encode(constraint.toString()));
        } else {
            if (DEBUG) Log.i(TAG, "No Constraint");
            baseUri = ContactsContract.Contacts.CONTENT_URI;
        }
        return new CursorLoader(this, baseUri, null, null, null, null);
    }
    
    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        if (DEBUG) Log.i(TAG, "Loader Callback: load finished");
        adapter.swapCursor(cursor);
    }
    
    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        if (DEBUG) Log.i(TAG, "Loader Callback: resetting");
        adapter.swapCursor(null);
    }
    }
    

    CursorAdapter – AdapterContacts.java

    public class AdapterContacts extends CursorAdapter {
    
    // Constants
    private static final String TAG = "AdapterContacts";
    private static final boolean DEBUG = true;
    
    // Variables
    private TextView mName;
    
    /**
     * Constructor
     */
    public AdapterContacts(Context context, Cursor c, int flags) {
        super(context, c, flags);
    }
    
    /**
     * Overrides
     */
    
    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        // Inflate the views that create each row of the dropdown list
        final LayoutInflater inflater = LayoutInflater.from(context);
        final LinearLayout ret = new LinearLayout(context);
        ret.setOrientation(LinearLayout.VERTICAL);
    
        mName = (TextView) inflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false);
        ret.addView(mName, new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
    
        /*
        int nameIdx = cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY);
        mName.setText(cursor.getString(nameIdx));
        */
    
        return ret;
    }
    
    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        // Fill the dropdown row with data from the cursor
        int nameIdx = cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY);
        String name = cursor.getString(nameIdx);
        ((TextView) ((LinearLayout) view).getChildAt(0)).setText(name);
    }
    
    @Override
    public String convertToString(Cursor cursor) {
        // Convert the dropdown list entry that the user clicked on
        // into a string that will fill the AutoCompleteTextView
        int nameCol = cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY);
        return cursor.getString(nameCol);
    }
    
    @Override
    public void changeCursor(Cursor newCursor) {
        newCursor.close();
    }
    }
    
    • 0
    • Reply
    • Share
      Share
      • Share on Facebook
      • Share on Twitter
      • Share on LinkedIn
      • Share on WhatsApp
      • Report

Sidebar

Related Questions

I have this situation where i have to start an activity from my mainActivity.
Hi and thanks for your help. I have the following situation. My Activity is
The situation: I have an activity with a spinner and a list with a
This is my situation. I have two activities: ONE and TWO. In TWO activity
I have a situation where I tested on a test device with the item
this is the current state/situation: I have an Activity which binds a Service which
I have a situation where I am making ajax requests to a server from
My situation is following. I have one Activity that contains three main parts. At
I have an activity that is started from another activity via a button and
I have a situation where I have 2 Activity objects (let's say empty and

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help
  • SEARCH

Footer

© 2021 The Archive Base. All Rights Reserved
With Love by The Archive Base

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.